Русский Español Português
preview
Extremal Optimization (EO)

Extremal Optimization (EO)

MetaTrader 5Trading |
102 0
Andrey Dik
Andrey Dik

Contents

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


Introduction

Many real-world problems, particularly trading ones, are characterized by complex discrete objective function landscapes with multiple local extrema, discontinuities, and non-differentiable regions, making classical gradient-based methods inapplicable. Numerous metaheuristic algorithms have been developed to solve such problems, and each approach has its own advantages and disadvantages in balancing exploration and exploitation of the search space.

Extremal Optimization (EO) is a metaheuristic optimization algorithm inspired by the Bak-Sneppen model. The algorithm was developed by Stefan Boettcher and Allon Percus in 1999 as a method inspired by the concept of self-organized criticality, according to which complex systems naturally evolve toward a critical state where avalanche-like changes of different scales occur. A population-based variant of EO was developed for continuous optimization using iterative population-level updates.


Implementation of the algorithm

The Bak-Sneppen model was developed to describe the evolution of ecosystems and demonstrate the phenomenon of self-organized criticality. This is a simple coevolution model that shows how local interactions can lead to global critical events.

Principles of the Bak-Sneppen model

Ecosystem of species:

  • N species are arranged in a chain (or on a lattice)
  • Each species i is assigned a fitness value fi ∈ [0,1]
  • Fitness represents the adaptability of a species to its environment.

Dynamics of evolution

At each time step:
1. Find the species with minimum fitness: fmin = min{fi}
2. Replace the fitness of this species and its neighbors with new random values
3. Repeat the process
Self-organized criticality
  • The system self-organizes to a critical state
  • The threshold value fc ≈ 0.67 occurs
  • Species with fitness < fc die out in avalanche-like events
  • Avalanche sizes follow a power law

Long periods of stagnation, when few changes are followed by sudden avalanche-like changes of varying scales in the absence of external control, the system self-organizes. Power-law changes affect the sizes of avalanches: P(s) ~ s^(-τ), where the lifetimes of species obey a power law in the absence of a characteristic scale. Changes in one species affect its neighbors. Global dynamics emerge from local interactions.

Boettcher and Perkus adapted the principles of the Bak-Sneppen model to create an optimization algorithm. Instead of improving the best, we improve the worst. Focusing on the worst elements is counterintuitive, but effective for breaking out of local optima. In the Bak-Sneppen model, evolution occurs through "punctuated equilibrium"—long periods of stasis interrupted by sudden changes. EO uses this principle: when only bad components are changed, and when changing one component dramatically improves the solution, improving one component may make other components "bad".

Extremal Optimization applies the principles of self-organized criticality — originally studied in ecosystem models—to optimization. The key innovation is the use of the natural tendency of complex systems to self-organize to automatically balance the exploration and exploitation of the search space. We will test this claim on practical problems. Let us now outline the algorithm in pseudocode.

Initialization:

  1. Create a population out of popSize agents 
  2. Initialize parameters
  3. Create arrays for ranking agents and components

First run (population initialization):

  1. For each agent in the population:
    • For each component (coordinates):
      • assign a random value within the acceptable range
      • apply discretization if necessary

Basic optimization loop:

For each agent in the population, execute:

  1. Agent ranking:
    • create a list of all agents with their fitness
    • sort agents by fitness (worst first for maximization)
  2. Select a target agent:
    • use the power-law distribution P(n) ∝ n^(-τ)
    • choose a rank according to this distribution
    • define the agent with the selected rank as the target one
  3. Target agent's component ranking:
    • For each component, calculate its fitness:
      • fitness = 1 - (normalized deviation from the best known value)
    • Sort components by fitness (worst first)
  4. Component selection and mutation:
    • use a power-law distribution to choose the component rank
    • replace the selected component with a new random value
    • check the boundaries and apply discretization

Results update:

  1. Sort the entire population by fitness (the best at the beginning)
  2. Update the global best solution if an improvement is found

Let's implement the algorithm in code. We will write a class that will represent the implementation of the EO algorithm, based on the optimization method using the principle of removing the worst elements to find solutions. The class inherits from the base class C_AO and additionally includes algorithm-specific parameters and methods. Constructor and destructor: initializing parameters and releasing resources respectively. Main parameters

  • popSize — population size, number of agents in the population;
  • tau — power-law distribution that determines the probabilities of selecting elements;
  • greedyStart — proportion of agents with greedy initialization;
  • eliteUpdate — proportion of agents participating in the update per iteration.
The params parameter array contains settings and copies the values of the original parameters for integration with the user interface or external control. Methods:
  • SetParams — extract the current parameter values from the array and apply them;
  • Init — initialize the population taking into account the ranges of parameters and the number of epochs;
  • Moving — execute one step of the algorithm updating the state of the population;
  • Revision — updates evaluations or prepare for the next stage. 
Internal structures:
  • RankedComponent — structure for storing the ranking of agent components by their quality (Fitness);
  • RankedAgent — structure for storing the ranking of the agents themselves according to their overall fitness.
Internal variables and parameters:
  • compRanks and agentRanks — arrays of structures for storing sorted components and agents;
  • taugreedyStarteliteUpdate control the behavior of the algorithm
Internal methods:
  • ApplyExtremalOptimization — basic mechanism for selecting and updating elements based on their ranks, taking into account the power law distribution.
  • CalculateComponentFitness — calculate the fitness of the agent component.
  • SelectRankByPowerLaw — select the rank of a component or agent using a power law distribution, which allows focusing on the worst elements.

The class is intended for the application of the extreme optimization algorithm, especially in problems requiring the search for a high-quality solution by successively eliminating the least fit components in the population.

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

  //------------------------------------------------------------------
  ArrayResize (compRanks, coords);
  ArrayResize (agentRanks, popSize);

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

The initialization method of the EO class is responsible for preparing the initial conditions for further execution. It accepts parameters for the minimum and maximum value ranges for the variables, as well as step sizes for changing them, and the number of epochs (iterations) if needed.

The method first calls the standard initialization procedure, which configures basic parameters and prepares common data structures. If this procedure fails, initialization is aborted.

If the initialization is successful, the method resizes the arrays used to store the ranked components and agents. The size of the component array is set according to the number of coordinates or variables of the problem, and the array of agents is set according to the given population size. Once these steps are completed, the method returns the result of successful initialization.

//————————————————————————————————————————————————————————————————————
//--- Main loop of the algorithm
void C_AO_EO::Moving ()
{
  // Initial population setup
  if (!revision)
  {
    int greedyCount = (int)(popSize * greedyStart);

    for (int i = 0; i < popSize; i++)
    {
      // Random initialization for the rest
      for (int c = 0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
        a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }

    revision = true;
    return;
  }

  // Apply Extremal Optimization ---------------------------------
  ApplyExtremalOptimization ();
}
//————————————————————————————————————————————————————————————————————

The ApplyExtremalOptimization method implements the main loop of the EO algorithm for updating the agent population. Its goal is to improve solutions by modifying agent components based on EO principles. The number of agents to update per iteration is determined taking into account the eliteUpdate parameter (the proportion of agents to update) and the population size. Agents are sorted based on their fitness. The algorithm ranks agents from worst to best so that the algorithm is designed for maximization. Using a power-law probability distribution, the agent that will be subject to change is selected.

For the selected agent, its components are ranked. Each component is assigned a fitness value to evaluate its contribution to the agent fitness. Components are ranked from worst to best based on their "fitness". A power-law probability distribution is used to select one of the agent's components for modification. This is done to make it more likely that components with the worst rank are selected.

The selected agent component is replaced with a random value within the allowed range, which is specified by the rangeMin and rangeMax parameters. The resulting value is checked for validity to ensure that it complies with the specified boundaries and step sizes.

    The method repeats these steps for each agent to be updated, implementing the key logic of the EO algorithm: selecting components with the worst performance and randomly replacing them in order to improve the fitness of solutions.

    //————————————————————————————————————————————————————————————————————
    //--- Apply Extremal Optimization
    void C_AO_EO::ApplyExtremalOptimization ()
    {
      // Number of agents to update in this iteration
      int numUpdates = MathMax (1, (int)(popSize * eliteUpdate));
    
      // Update selected agents based on EO principle
      //for (int update = 0; update < numUpdates; update++)
      for (int update = 0; update < popSize; update++)
      {
        // Step 1: Select an agent to modify
        // Use ranking by overall fitness
        int targetAgent;
    
        // Rank agents by fitness (from worst to best for maximization)
        for (int i = 0; i < popSize; i++)
        {
          agentRanks [i].idx = i;
          agentRanks [i].fitness = a [i].f;
        }
    
        // Sort (worst first to maximize)
        for (int i = 0; i < popSize - 1; i++)
        {
          for (int j = i + 1; j < popSize; j++)
          {
            if (agentRanks [i].fitness > agentRanks [j].fitness)
            {
              RankedAgent temp = agentRanks [i];
              agentRanks [i] = agentRanks [j];
              agentRanks [j] = temp;
            }
          }
        }
    
        // Select an agent according to the power-law distribution
        int rank    = SelectRankByPowerLaw (popSize);
        targetAgent = agentRanks [rank].idx;
    
        // Step 2: Rank the components of the selected agent
        for (int c = 0; c < coords; c++)
        {
          compRanks [c].agentIdx     = targetAgent;
          compRanks [c].componentIdx = c;
          compRanks [c].fitness      = CalculateComponentFitness (targetAgent, c);
        }
    
        // Sort components (worst first)
        for (int i = 0; i < coords - 1; i++)
        {
          for (int j = i + 1; j < coords; j++)
          {
            if (compRanks [i].fitness > compRanks [j].fitness)
            {
              RankedComponent temp = compRanks [i];
              compRanks [i] = compRanks [j];
              compRanks [j] = temp;
            }
          }
        }
    
        // Step 3: Select a component to change according to P(n) ∝ n^(-τ)
        int compRank = SelectRankByPowerLaw (coords);
        int compIdx  = compRanks [compRank].componentIdx;
    
        // Step 4: Replace the selected component with a new random value
        // This is the key principle of EO - unconditional replacement with random
        a [targetAgent].c [compIdx] = u.RNDfromCI (rangeMin [compIdx], rangeMax [compIdx]);
    
        // Check boundaries
        a [targetAgent].c [compIdx] = u.SeInDiSp (a [targetAgent].c [compIdx],
                                                  rangeMin [compIdx],
                                                  rangeMax [compIdx],
                                                  rangeStep [compIdx]);
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The CalculateComponentFitness method calculates the fitness value for a single component of the agent. This function plays an important role in the Extremal Optimization (EO) algorithm, evaluating the contribution of each component to the fitness of the solution. The algorithm uses a simple metric based on calculating "fitness" as the relative contribution of a component to the fitness.

    Initially, "fitness" is initialized to zero. Next, the range of component values (range) is calculated. If this range is greater than zero, the normalized deviation is calculated. The deviation is calculated as the absolute value of the difference between the agent component value and the base value of cB, normalized by the range of component values. Fitness is calculated by subtracting "deviation" from 1.0. This makes "fitness" inversely proportional to the deviation: the smaller the deviation of a component from the target value, the higher its "fitness".

    Thus, the function returns a fitness score for a particular component, which indicates how well the current value of the component matches the target value, which is necessary for further ranking of components.

    //————————————————————————————————————————————————————————————————————
    //--- Calculate the fitness component
    double C_AO_EO::CalculateComponentFitness (int agentIdx, int componentIdx)
    {
      // For the general optimization problem we use a simple metric
      // λi = relative contribution of the component to the fitness
    
      double fitness = 0.0;
    
      double range = rangeMax [componentIdx] - rangeMin [componentIdx];
      if (range > 0)
      {
        // Normalized deviation
        double deviation = MathAbs (a [agentIdx].c [componentIdx] - cB [componentIdx]) / range;
        fitness =1.0 - deviation;// Invert so that bigger = better
      }
    
      return fitness;
    }
    //————————————————————————————————————————————————————————————————————
    

    The SelectRankByPowerLaw method implements the selection of the position (rank) of an element from a list based on a power probability distribution. This method is key to the EO algorithm, since it is through it that the selection of agents and components for modification is made. The method takes maxRank as input, which defines the maximum rank of the element.

    The method is based on the principle of inverse transformation to obtain a random value, following the power law P(n) ∝ n^(-τ), where: n is the rank of the element; τ (tau) is the parameter determining the shape of the power-law distribution.

    Depending on the value of the "tau" parameter, two scenarios are realized:

    1. General case (τ ≠ 1.0):

      • a normalizing factor "norm" is calculated, which ensures the correct scaling of probabilities;
      • 'r' random number in the range [0, 1) is generated using u.RNDprobab();
      • the inverse transform formula is used to calculate the "rank" based on "r", "norm" and "τ";
      • the rank is limited to [0, maxRank - 1] to avoid array out-of-bounds.
    2. Special case (τ = 1.0):

      • the normalizing factor "norm" is calculated for the case τ = 1.0;
      • a special formula is used to calculate the "rank";
      • the rank is limited within the range [0, maxRank - 1].

    As a result, the method returns an integer "rank", which indicates the selected position of the element in the ranked list. This selection is probabilistic, that is, elements with lower rank (worst) have a higher probability of being selected, in accordance with the principles of EO.

    //————————————————————————————————————————————————————————————————————
    //--- Select a rank according to a power-law distribution
    int C_AO_EO::SelectRankByPowerLaw (int maxRank)
    {
      // P(n) ∝ n^(-τ), where n is a rank from 1 to maxRank
      // Use the inverse transformation method
    
      double r = u.RNDprobab ();
    
      if (tau != 1.0)
      {
        // General case: inverse transform for P(n) ∝ n^(-τ)
        double norm = (1.0 - MathPow (maxRank + 1.0, 1.0 - tau)) / (1.0 - tau);
        double x = r * norm;
        int rank = (int)MathPow ((1.0 - tau) * x + 1.0, 1.0 / (1.0 - tau)) - 1;
    
        if (rank >= maxRank) rank = maxRank - 1;
        if (rank < 0) rank = 0;
    
        return rank;
      }
      else
      {
        // Special case τ = 1: P(n) ∝ 1/n
        double norm = MathLog (maxRank + 1.0);
        int rank = (int)(MathExp (r * norm) - 1.0);
    
        if (rank >= maxRank) rank = maxRank - 1;
        if (rank < 0) rank = 0;
    
        return rank;
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The "Revision" method is designed to update information about the best solutions found during the operation of the EO algorithm. Its task is to determine the current best solution and update global variables that store information about it. A temporary array of aT is created to store the sorted agents. The 'a' population of agents is sorted using the built-in u.Sorting function. The sorting is performed to maximize, that is, the best agents (those with the highest 'f' fitness function value) are placed at the beginning of the 'a' array (which is the main data structure) containing information about the agents, including the values of their 'c' components and the value of the 'f' fitness function.

    After sorting, it is checked whether the 'f' fitness function value of the best agent (i.e. a[0]) is greater than the current best fB value (stores the fitness function value of the best solution found so far). If the current best agent is better than the global best solution, then the values of the best agent's components (a[0].c) are copied into the cB array, which likely stores the components of the best solution, and fB is updated to the value of the best agent's fitness function (a[0].f).

    Thus, the Revision function updates the information about the best solution, preserving its components and the value of the fitness function, and sorts the population so that the best solutions are always available at the beginning of the array. 

    //————————————————————————————————————————————————————————————————————
    //--- Update the best solutions
    void C_AO_EO::Revision ()
    {
      // Sort the population for MAXIMIZATION
      static S_AO_Agent aT [];
      ArrayResize (aT, popSize);
    
      // Use the built-in sorting function
      u.Sorting (a, aT, popSize);
    
      // Update the global best solution
      if (a [0].f > fB)
      {
        ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY);
        fB = a [0].f;
      }
    }
    //————————————————————————————————————————————————————————————————————
    


    Test results

    After testing, the algorithm did not show good results, so I decided to review all the original methods and make a modified version aimed at improving the convergence of the algorithm.

    EO|Extremal Optimization (Boettcher-Percus)|50.0|1.4|0.5|0.3|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.5146369337195529
    25 Hilly's; Func runs: 10000; result: 0.29089804555433085
    500 Hilly's; Func runs: 10000; result: 0.25192095557138877
    =============================
    5 Forest's; Func runs: 10000; result: 0.367128650332966
    25 Forest's; Func runs: 10000; result: 0.19477408852361866
    500 Forest's; Func runs: 10000; result: 0.15367465708144543
    =============================
    5 Megacity's; Func runs: 10000; result: 0.2584615384615384
    25 Megacity's; Func runs: 10000; result: 0.12707692307692314
    500 Megacity's; Func runs: 10000; result: 0.09413846153846227
    =============================
    All score: 2.25271 (25.03%)

    Below is my own interpretation of this optimization method. We implement a new class that inherits the base optimization class. 

    The constructor sets the initial parameters of the algorithm: population size, the number of worst solutions to be increased (improved), the probability of mutation, and the degrees of distribution for selection and mutation. The parameters are stored in an array of structures for further modification and customization.

    The SetParams method updates the class's internal variables based on the values in the parameters array. Methods are declared for initializing the algorithm, performing movement steps (updating the state), and revision (reevaluates solutions or prepares the next iteration). The private part of the class stores variables that track the current epoch and the total number of epochs of the algorithm's execution. A method for mutating a specific component of a solution in a population is defined, which is a key part of the implemented algorithm. 

    //————————————————————————————————————————————————————————————————————
    class C_AO_EOm : public C_AO
    {
      public: //----------------------------------------------------------
      ~C_AO_EOm () { }
      C_AO_EOm ()
      {
        ao_name = "EOm";
        ao_desc = "Extremal Optimization M";
        ao_link = "https://www.mql5.com/en/articles/18755";
    
        popSize        = 50;      // Population size
        popRaising     = 3;       // Boost the worst agents
        mutationRate   = 0.1;     // Mutation probability
        powCh          = 2.0;     // Selection power-law exponent
        powMut         = 8.0;     // Mutation power-law exponent 
    
        ArrayResize (params, 5);
    
        params [0].name = "popSize";        params [0].val = popSize;
        params [1].name = "popRaising";     params [1].val = popRaising;
        params [2].name = "mutationRate";   params [2].val = mutationRate;
        params [3].name = "powCh";          params [3].val = powCh;
        params [4].name = "powMut";         params [4].val = powMut;
      }
    
      void SetParams ()
      {
        popSize        = (int)params [0].val;
        popRaising     = (int)params [1].val;
        mutationRate   = params      [2].val;
        powCh          = params      [3].val;
        powMut         = params      [4].val;
      }
    
      bool Init (const double &rangeMinP  [],
                 const double &rangeMaxP  [],
                 const double &rangeStepP [],
                 const int     epochsP = 0);
    
      void Moving   ();
      void Revision ();
    
      //------------------------------------------------------------------
      int    popRaising;       // Boost the worst agents
      double mutationRate;     // Mutation probability
      double powCh;            // Selection power-law exponent
      double powMut;           // Mutation power-law exponent
    
      private: //---------------------------------------------------------
      int    currentEpoch;     // current epoch
      int    totalEpochs;      // total number of epochs
    
      void MutateComponent (int agentIdx, int componentIdx);
    };
    //————————————————————————————————————————————————————————————————————
    

    The Init method is responsible for initializing the algorithm. The function takes as input arrays with minimum, maximum values and step for optimization parameters, as well as the total number of epochs (cycles) of the algorithm operation.

    Inside the method, the StandardInit function from the C_AO base class is first called. This function performs general initialization, memory allocation, or setting up basic parameters related to ranges and steps for optimization parameters.

    If StandardInit fails, then the Init function also exits, returning 'false'. If StandardInit is successful, the method initializes the variables currentEpoch (current epoch) to 0 and totalEpochs (total number of epochs) to the value received in the epochsP parameter.

    At the end, the method returns 'true', signaling successful initialization. Thus, the method prepares the algorithm for work by setting the initial parameters and epoch counters.

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

    The Moving method represents the main cycle of the extreme optimization algorithm. It is responsible for the evolution of the solution population in the process of searching for the optimal solution. Epoch increment: the currentEpoch counter is incremented. Population initialization (first epoch only): if the 'revision' flag (indicating the need to revise the decisions) is equal to 'false' (i.e. in the first cycle of the algorithm), the population is initialized.

    For each "agent" (solution) in the population, for each "coordinate" (solution parameter), a random value is generated in a given range (rangeMin, rangeMax) for the current coordinate. The SeInDiSp discretization is used to ensure that the parameter values correspond to the specified steps. After initialization, "revision" is set to 'true' so that this block of code is executed only once. The method terminates. 

    Applying extremal optimization in subsequent epochs: a temporary array aT is created to store new (mutated) solutions. For each agent in the population, the aT[i] structure is initialized. For each solution coordinate, a random number is generated and raised to the power of powCh. This is used to select the "parent".

    The random number is scaled to select the parent index in the ind population. The mutation type is determined: if the random number is less than mutationRate (the mutation is enabled with mutationRate probability), then the mutation is applied according to the PowerDistribution power-law distribution law to the value of the parent coordinate. Otherwise, there is a "directed movement" towards the best solution with the addition of random noise. The new value is calculated as the sum of the parent coordinate value, a random number, and the difference between cB[c] (the best coordinate found) and the parent coordinate. The SeInDiSp discretization is used. After mutation, all coordinates from the temporary array aT are copied to the 'a' main population array.

    Thus, the method implements an evolution cycle, where at each step (epoch) the solutions mutate (change), using the mechanisms of selection, random mutation and directed search, with the aim of improving their values (minimizing or maximizing the objective function) in order to ultimately find the optimal solution.

    //————————————————————————————————————————————————————————————————————
    //--- Main loop of the algorithm
    void C_AO_EOm::Moving ()
    {
      currentEpoch++;
    
      // Initial population setup
      if (!revision)
      {
        for (int i = 0; i < popSize; i++)
        {
          for (int c = 0; c < coords; c++)
          {
            a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
            a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
    
        revision = true;
        return;
      }
    
      //Apply Extremal Optimization---------------------------------------
      static S_AO_Agent aT []; ArrayResize (aT, popSize);
    
      for (int i = 0; i < popSize; i++)
      {
        aT [i].Init (coords);
    
        for (int c = 0; c < coords; c++)
        {
          double rnd = u.RNDprobab (); rnd = pow (rnd, powCh);
          int ind = (int)u.Scale (rnd, 0.0, 1.0, 0, popSize - 1);
    
          // Select the mutation type
          double mutType = u.RNDprobab ();
    
          if (mutType < mutationRate)
          {
            aT [i].c [c] = u.PowerDistribution (a [ind].c [c], rangeMin [c], rangeMax [c], powMut);
          }
          else
          {
            // Directed movement towards the better with noise
            aT [i].c [c] = a [ind].c [c] + u.RNDprobab () * (cB [c] - a [ind].c [c]);
          }
    
          // Check boundaries
          aT [i].c [c] = u.SeInDiSp (aT [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    
      for (int i = 0; i < popSize; i++) ArrayCopy (a [i].c, aT [i].c);
    }
    //————————————————————————————————————————————————————————————————————
    

    The method is responsible for updating information about the best solutions during the algorithm's operation. 

    Sorting the population: A temporary array of aT is created to store a copy of the population. The population 'a' is sorted by the value of the objective function. The u.Sorting method is used for sorting. 

    Update the global best solution: it is checked whether the value of the objective function "f" of the first element of the population (i.e. the best solution) is better than the current global best value of fB. If the current one is better, then cB is copied from the coordinates of the first element of a[0].c, and fB is updated with the value of f of the better solution. 

    Worst solution update: fW is updated with the value of the function of the last element of the population. 

    Raising the worst solutions: in a loop executed popRaising times, the worst solutions in the population are raised: for popRaising the worst agents (located at the end of the sorted array of 'a'), the value of the objective function 'f' is recalculated as a random value located between fW (the worst current result) and fB (the best current result). This is done to introduce variety or to shift the worst solutions towards the better. 

    Resorting the population: population 'a' is re-sorted using the u.Sorting method. This is necessary to bring the population back into order after changes in the 'f' values.

    //————————————————————————————————————————————————————————————————————
    //--- Update the best solutions
    void C_AO_EOm::Revision ()
    {
      // Sort the population  --------------------------------------------
      static S_AO_Agent aT []; ArrayResize (aT, popSize);
      u.Sorting (a, aT, popSize);
    
      // Update the global best solution
      if (a [0].f > fB)
      {
        ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY);
        fB = a [0].f;
      }
    
      fW = a [popSize - 1].f;
    
      //------------------------------------------------------------------
      for (int i = 0; i < popRaising; i++)
      {
        a [popSize - 1 - i].f = u.RNDfromCI (fW, fB);
      }
    
      u.Sorting (a, aT, popSize);
    }
    //————————————————————————————————————————————————————————————————————
    

    Now the algorithm (in some sense inheriting the ideas of EO, albeit in a modified way), uses a power-law distribution to select donors (not necessarily bad ones).

    a [popSize - 1 - i].f = u.RNDfromCI (fW, fB);

    I have added a feature not found in EO descriptions: the algorithm essentially "resurrects" the worst agents, assigning them a random fitness in the range from the worst to the best in the population. PowerDistribution for mutation is an interpretation of the "power law distribution mutation". Directed movement towards the better (for mutType >= mutationRate ) — addition to accelerate convergence.

    Now we can look at the results of the modified version. We can include the following results in our rating table.

    EOm|Extremal Optimization Mod|50.0|3.0|0.1|2.0|8.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.7616651321732368
    25 Hilly's; Func runs: 10000; result: 0.7724295992586084
    500 Hilly's; Func runs: 10000; result: 0.3174703398632668
    =============================
    5 Forest's; Func runs: 10000; result: 0.9999936711143977
    25 Forest's; Func runs: 10000; result: 0.7675163269928252
    500 Forest's; Func runs: 10000; result: 0.23527203643380376
    =============================
    5 Megacity's; Func runs: 10000; result: 0.7476923076923077
    25 Megacity's; Func runs: 10000; result: 0.5396923076923076
    500 Megacity's; Func runs: 10000; result: 0.14249230769230903
    =============================
    All score: 5.28422 (58.71%)

    The visualization shows a large scatter of results for low-dimensional functions (green lines).

    Hilly

    EOm on the Hilly test function

    Forest

    EOm on the Forest test function

    Megacity

    EOm on the Megacity test function

    After testing, the EOm algorithm ranks 12th in our ranking table.

    # 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)
    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
    26BSAbird swarm algorithm0.893060.649000.262501.804550.924200.711210.249391.884790.693850.326150.100121.120124.80953.44
    27DEAdolphin_echolocation_algorithm0.759950.675720.341711.777380.895820.642230.239411.777460.615380.440310.151151.206844.76252.91
    28HSharmony search0.865090.687820.325271.878180.999990.680020.095901.775920.620000.422670.054581.097254.75152.79
    29SSGsaplings sowing and growing0.778390.649250.395431.823080.859730.624670.174291.658690.646670.441330.105981.193984.67651.95
    30BCOmbacterial chemotaxis optimization M0.759530.622680.314831.697040.893780.613390.225421.732590.653850.420920.144351.219124.64951.65
    31ABOafrican buffalo optimization0.833370.622470.299641.755480.921700.586180.197231.705110.610000.431540.132251.173784.63451.49
    32(PO)ES(PO) evolution strategies0.790250.626470.429351.846060.876160.609430.195911.681510.590000.379330.113221.082554.61051.22
    33FBAFractal-Based Algorithm0.790000.651340.289651.730990.871580.568230.188771.628580.610770.460620.123981.195374.55550.61
    34TSmtabu search M0.877950.614310.291041.783300.928850.518440.190541.637830.610770.382150.121571.114494.53650.40
    35BSObrain storm optimization0.937360.576160.296881.810410.931310.558660.235371.725340.552310.290770.119140.962224.49849.98
    36WOAmwale optimization algorithm M0.845210.562980.262631.670810.931000.522780.163651.617430.663080.411380.113571.188034.47649.74
    37AEFAartificial electric field algorithm0.877000.617530.252351.746880.927290.726980.180641.834900.666150.116310.095080.877544.45949.55
    38AEOartificial ecosystem-based optimization algorithm0.913800.467130.264701.645630.902230.437050.214001.553270.661540.308000.285631.255174.45449.49
    39CAmcamel algorithm M0.786840.560420.351331.698590.827720.560410.243361.631490.648460.330920.134181.113564.44449.37
    40ACOmant colony optimization M0.881900.661270.303771.846930.858730.586800.150511.596040.596670.373330.024720.994724.43849.31
    41CMAEScovariance_matrix_adaptation_evolution_strategy0.762580.720890.000001.483470.820560.796160.000001.616720.758460.490770.000001.249234.34948.33
    42BFO-GAbacterial foraging optimization - ga0.891500.551110.315291.757900.969820.396120.063051.428990.726670.275000.035251.036924.22446.93
    43SOAsimple optimization algorithm0.915200.469760.270891.655850.896750.374010.169841.440600.695380.280310.108521.084224.18146.45
    44ABHAartificial bee hive algorithm0.841310.542270.263041.646630.878580.477790.171811.528180.509230.338770.103970.951974.12745.85
    45ACMOatmospheric cloud model optimization0.903210.485460.304031.692700.802680.378570.191781.373030.623080.244000.107950.975034.04144.90
    RWrandom walk0.487540.321590.257811.066940.375540.219440.158770.753750.279690.149170.098470.527342.34826.09


    Summary

    The presented compact modified implementation of EOm demonstrates an interesting example in the field of metaheuristic optimization: a significant deviation from theoretical principles can lead to improved practical results. The algorithm, which ranked 12th among 45 population-based methods, is actually a hybrid that retains only the key idea of the power-law distribution from the original EO. 

    "Resurrection" mechanism of the worst agents prevents premature convergence, maintain population diversity, and create additional opportunities for exploration. Simplification of the computational structure: abandoning component-wise evaluation reduces computational complexity, and working directly with solutions speeds up convergence.

    The success of the modified version of EO confirms an important principle in the design of metaheuristics: the efficiency of an algorithm is determined not by its fidelity to the original idea, but by the balance between exploration and exploitation of the search space . The presented implementation, despite its departure from the classical principles of Extremal Optimization, demonstrates high convergence speed, competitive results, and ease of implementation and configuration.

    This makes it a useful tool in the arsenal of population optimization methods, especially for problems where the balance between solution quality and computational cost is important.

    tab

    Figure 1. Color gradation of algorithms according to the corresponding tests

    tab

    Figure 2. 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)

    EOm pros and cons:

    Pros:

    1. Simple implementation
    2. Fast and efficient
    3. Good results on discrete problems

    Cons:

    1. Large scatter of results on low-dimension functions
    2. Average results on "smooth" low-dimensional problems

    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 in 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_EOm.mq5
    ScriptEOm test stand

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

    Attached files |
    EOm.zip (247.28 KB)
    Building a Type-Safe Event Bus in MQL5: Decoupling EA Components Without Global Variables Building a Type-Safe Event Bus in MQL5: Decoupling EA Components Without Global Variables
    A typed publish-subscribe event bus in MQL5 replaces global variables and direct cross-references. Using an abstract listener interface and an enum-indexed subscription table, a signal engine, order manager, and drawdown monitor communicate only through the bus, with no shared state. The article analyzes dispatch overhead, pointer validation, and recursive publish risks, helping you design decoupled, testable EAs.
    Neural Networks in Trading: Actor—Director—Critic Neural Networks in Trading: Actor—Director—Critic
    We invite you to explore the Actor-Director-Critic framework, which combines hierarchical learning and a multi-component architecture for creating adaptive trading strategies. In this article, we take a detailed look at how using the Director to classify the Actor's actions helps to effectively optimize trading decisions and improve the robustness of models in financial market conditions.
    Engineering a Self-Healing Expert Advisor in MQL5 (Part 2): Restart-Safe Virtual Trade Protection Engineering a Self-Healing Expert Advisor in MQL5 (Part 2): Restart-Safe Virtual Trade Protection
    Build a restart-aware virtual protection layer on top of the SQLite persistence from Part 1. The EA reconstructs hidden stop-loss and take-profit after restart, verifies current price against recovered exits, and closes or continues positions accordingly. The result is a consistent recovery path that detects managed positions and sustains safe runtime management.
    Implementing Partial Position Closing in MQL5 Implementing Partial Position Closing in MQL5
    This article develops a class for managing partial position closing in MQL5 and then integrates it into an Order Blocks Expert Advisor. It also presents test results comparing the strategy with and without partial position closing, and analyzes the conditions under which this approach can help provide and maximize profit. In conclusion, partial position closing can be highly beneficial in trading strategies, especially those focused on wider price movements.