Русский Português
preview
Eagle Strategy (ES)

Eagle Strategy (ES)

MetaTrader 5Trading |
415 0
Andrey Dik
Andrey Dik

Contents

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


Introduction

In the world of programming, especially when developing trading EAs, effective optimization plays a critical role. Highly specialized problems of finding an optimal solution among a huge space of possible options require the use of advanced algorithms. Traditional optimization methods often prove ineffective, getting stuck in local optima and failing to find a globally better solution.

Accordingly, increasing attention is being given to metaheuristic algorithms inspired by nature, as well as to methods that have undergone natural evolution. These algorithms are capable of finding good, and often near-optimal, solutions, even in problems where computation speed is of paramount importance.

Against the backdrop of increasingly complex and resource-intensive tasks, we will examine another promising approach today: Eagle Strategy (ES). This algorithm, inspired by the eagle's hunting behavior, is a new metaheuristic designed to solve optimization problems by simulating a strategy of visual search and dynamic pursuit of prey.


Implementation of the algorithm

Imagine a golden eagle soaring high in the sky in search of prey. Its hunting strategy is remarkably efficient and consists of two distinct stages: first, it flies at high altitude, surveying a vast area with chaotic zigzag movements, and then, having spotted a target, it dives down rapidly, concentrating all its efforts on a specific prey. It is this natural wisdom that formed the basis of the Eagle Strategy algorithm, developed in 2010 to solve complex optimization problems.

In the first stage, the algorithm uses so-called Levy flights — a mathematical model that describes movement in space. Unlike a typical random walk, where the steps are roughly uniform, a Levy flight consists of many small steps interspersed with rare but very long jumps. 

When the algorithm finds a promising area (like an eagle spotting prey), the second stage is activated - an intensive local search using the firefly algorithm. We have already discussed such a well-known algorithm in one of the articles. Several search agents, like fireflies in the night, are attracted to brighter (successful) neighbors. The brightness of the firefly corresponds to the quality of the solution it finds, and the attraction weakens exponentially with distance. This creates a balance between exploring the surrounding area and moving towards the best known solution.

The key innovation of ES is the cyclical alternation of these two modes. After an intensive local search, the algorithm switches back to global exploration, making a long jump into a new, unexplored region of the search space. This prevents premature convergence and allows finding a global optimum even in very complex landscapes with many local optima.

The algorithm parameters are intuitive and easy to configure. The population size determines the number of simultaneously exploring agents, the Levy parameter controls the ratio between short and long jumps, the hypersphere radius defines the area of intensive local search, and the randomization coefficient adds an element of randomness to overcome local traps. This flexibility allows the algorithm to be adapted to a specific problem without a deep understanding of mathematical theory.

The philosophy of the ES algorithm is simple and elegant: global vision combined with local precision. Just as an eagle combines observational flight with a targeted attack, the algorithm balances between exploring unknown areas and exploiting promising solutions it finds.

The key features of the algorithm are: automatic switching between phases as the solution improves, adaptive parameters (λ decreases during stagnation, the step size decreases over time) and a balance between exploring new areas and exploiting the solutions found.

eagle_strategy

Figure 1. ES algorithm in action

The illustration shows the two operating phases of the algorithm, the Levy flight trajectories (red dotted lines), the local search hypersphere (blue circle), the movement of fireflies inside the sphere (green dots with halos), and the contour lines of the optimization function.

Let's prepare the pseudocode.

INITIALIZATION:
1. Create a population of agents with random positions
2. Set global search flag (phase = global)
3. Initialize stagnation and progress counters

MAIN LOOP (until the stop criterion is reached):
  
  IF (phase = global):
    FOR each agent:
      - generate a Levy step using Mantegna algorithm
      - calculate the adaptive step scale (larger at the beginning, smaller at the end)
      - update position: new_position = current + Levy_step × scale
      - apply boundary constraints
    
    IF (an improvement on the global best is found):
      - Switch to the local phase
      - Find the best agent as a local search center
      - Reset the stagnation counter
    OTHERWISE:
      - Increase the stagnation counter
      - IF (stagnation > 5 iterations):
        - Reduce the λ parameter for more aggressive flights
  
  ELSE (phase = local):
    With a probability of 80%:
      - identify agents in a hypersphere of radius 0.1 around the best one
      - IF (agents < 5):
        - Take 5 nearest neighbors or 30% of the population
      
      FOR each agent in the local group:
        FOR each other agent in the group:
          IF (another agent is better):
            - calculate the attractiveness β = β₀ × exp(-γ × distance²)
            - move agent: position += β × (best - current) + random_noise
    
    With a probability of 20%:
      - Partially copy the coordinates of the global best agent to random ones
    
    - Increase local iteration counter
    - IF (20 local iterations completed):
       - Return to the global phase
       - Restore the λ original parameter

  UPDATE:
    FOR each agent:
      - calculate fitness
      - update personal best
      - update global best

In this implementation of the ES algorithm, a numerical method is applied to generate numbers with the Levy distribution — Mantegna algorithm. It was proposed by R. N. Mantegna in 1994 and became the standard method for simulating Levy flight in optimization algorithms. Mantegna mathematically proved that the ratio of two specially scaled Gaussian quantities yields a distribution very close to the Levy distribution for the range of values important in practical applications. Its essence is as follows:

// Calculating sigma for Mantegna algorithm
double numerator   = Gamma(1.0 + lambda) * MathSin(M_PI * lambda / 2.0);
double denominator = Gamma((1.0 + lambda) / 2.0) * lambda * MathPow(2.0, (lambda - 1.0) / 2.0);
double sigma = MathPow(numerator / denominator, 1.0 / lambda);

// Generate u and v from normal distributions
double u_val = GenerateGaussian() * sigma;
double v_val = MathAbs(GenerateGaussian());

// Calculate Levy step
levyStep[c] = u_val / MathPow(v_val, 1.0 / lambda);

Take two random variables:

  • u - from the N(0, σ²) normal distribution
  • v - from the N(0, 1) normal distribution
Special equation for σ:
σ = [Γ(1+λ) × sin(πλ/2) / (Γ((1+λ)/2) × λ × 2^((λ-1)/2))]^(1/λ)

where Γ is the gamma function, λ is the Levy distribution parameter (1 < λ ≤ 3).

It is important to note that when determining the gamma function (to calculate the σ parameter in the Mantegna method), the Lanczos approximation is used, which is a highly accurate numerical method for calculating the gamma function. This is one of the most efficient ways to calculate Γ(z) and is implemented in the code as a separate function, which we will discuss last.

The basic equation is as follows:

Γ(z+1) = √(2π) × ((z+g+0.5)^(z+0.5)) × e^(-(z+g+0.5)) × Ag(z)

where: g is a parameter (usually 7); Ag(z) is a series with pre-calculated coefficients, with g=7 and 9 coefficients it gives the accuracy of approximately 15 significant digits.

For z < 0.5, the reflection formula uses the ratio, which allows the gamma function to be calculated for all real numbers:

Γ(z) × Γ(1-z) = π / sin(πz)

Without efficient gamma function computation, generating Levy flights would be computationally expensive, which would significantly slow down the entire optimization algorithm.

Levy final step:

step = u / |v|^(1/λ)

The algorithm generates a step sequence with behavior typical of Levy flights: many small steps (local exploration), rare but very large jumps (global exploration), and a power-law distribution of step lengths.

The advantages of this method combine the simplicity of the solution, because only a normal distribution generator is required together with stability and numerical robustness of the algorithm. It is this property that makes Levy flights effective for optimization — they provide an optimal balance between detailed exploration of local areas and rapid transition to new regions of the search space.

After a detailed examination of the methods used in the ES algorithm, we can confidently move on to writing the C_AO_ES class, which represents the implementation of an optimization method based on the eagle hunting strategy and is inherited from the C_AO base class. The method uses a two-step approach: first, a global search is performed to identify potentially promising areas, then a local search within the selected search area is performed to refine the result.

The popSize population size specifies the number of "candidate" solutions participating in the search. The "lambda" Levy parameter controls the distribution for random steps. The "sphereRadius" hypersphere radius defines the area for local search. The number of local search iterations "localIterations" specifies how many times the solution is refined within the hypersphere. The "alpha" and "beta0" randomization and attractiveness parameters regulate components of the search models, such as random movements and the influence of "light" (in metaphorical terms). 

Global exploration phase (GlobalExploration) is focused on finding "promising" regions throughout the search space using random steps generated by Levy distribution. This approach enables broad exploration and scales well to large search spaces.

The local exploitation phase (LocalExploitation) performs a more thorough search within the hypersphere around the selected point. In this case, smaller and more precise steps are used, corresponding to local optimization.

Levy step generation (GenerateLevyStep) generates random moves using the Levy distribution, this provides both small and large jumps in the search space to balance exploration and exploitation.

The class contains mechanisms for tracking the search progress, such as remembering the best solution found, stagnation counters (if the solution does not improve), as well as epoch loops to limit the algorithm operation by time or iterations, calculations of distribution parameters for random steps, in particular, the generation of Gaussian and Levy distributions, and methods for managing the search phases, which ensures switching between the global and local stages, as well as control over their operation.

Overall, the method presents a search strategy that combines extensive exploration of the space to identify potential areas, followed by careful local optimization to achieve accurate solutions, using parameters and mechanisms that allow its behavior to be adapted to specific problems and conditions.

//————————————————————————————————————————————————————————————————————
class C_AO_ES : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_ES () { }
  C_AO_ES ()
  {
    ao_name = "ES";
    ao_desc = "Eagle Strategy";
    ao_link = "https://www.mql5.com/en/articles/18460";

    popSize         = 100;   // population size
    lambda          = 1.0;  // Levy distribution parameter (1 < λ ≤ 3)
    sphereRadius    = 0.1;  // hypersphere radius for local search
    localIterations = 20;   // number of local search iterations
    alpha           = 0.1;  // randomization parameter for Firefly
    beta0           = 1.2;  // initial attractiveness

    ArrayResize (params, 6);

    params [0].name = "popSize";         params [0].val = popSize;
    params [1].name = "lambda";          params [1].val = lambda;
    params [2].name = "sphereRadius";    params [2].val = sphereRadius;
    params [3].name = "localIterations"; params [3].val = localIterations;
    params [4].name = "alpha";           params [4].val = alpha;
    params [5].name = "beta0";           params [5].val = beta0;
  }

  void SetParams ()
  {
    popSize         = (int)params [0].val;
    lambda          = params      [1].val;
    sphereRadius    = params      [2].val;
    localIterations = (int)params [3].val;
    alpha           = params      [4].val;
    beta0           = params      [5].val;
  }

  bool Init (const double &rangeMinP  [],  // minimum values
             const double &rangeMaxP  [],  // maximum values
             const double &rangeStepP [],  // step change
             const int     epochsP = 0);   // number of epochs

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  double lambda;          // Levy distribution parameter (1 < λ ≤ 3)
  double sphereRadius;    // hypersphere radius for local search
  int    localIterations; // number of local search iterations
  double alpha;           // randomization parameter
  double beta0;           // initial attractiveness

  private: //---------------------------------------------------------
  double gamma_es;           // light absorption coefficient
  double levyStep [];        // array for Levy steps

  // Phase tracking
  bool   inLocalSearchPhase; // local search flag
  int    localSearchCenter;  // local search center
  int    localSearchCounter; // local search iteration counter

  // Monitoring convergence
  double prevBestFitness;    // previous best value
  int    stagnationCounter;  // stagnation counter

  // Tracking epochs
  int    epochCurrent;       // current epoch
  int    epochMax;           // maximum number of epochs

  // Auxiliary methods
  void   GlobalExploration  ();
  void   LocalExploitation  ();
  void   GenerateLevyStep   ();
  double GenerateGaussian   ();
  double Gamma              (double z);
};
//————————————————————————————————————————————————————————————————————

The Init method of the C_AO_ES class initializes the optimization algorithm before starting the search. First, the StandardInit method is called, which is responsible for the standard initialization of the algorithm. It configures the general parameters and data structures. If StandardInit fails, then the entire method returns 'false', signaling that the initialization was unsuccessful.

Next, memory is allocated for the levyStep array with a size equal to the number of "coords" coordinates. This array will be used to store the steps generated according to the Levy distribution. The inLocalSearchPhase flag is set to 'false', the algorithm is initially in the global search phase. The localSearchCenter and localSearchCounter variables are set to "0" to prepare the counters for local search.

Initialization of convergence parameters:

  • prevBestFitness is set to the minimum possible value so that the first solution found is guaranteed to be considered better than the previous one.
  • stagnationCounter is set to "0" to track the number of iterations without improving the best solution found.

Initialization of epoch parameters:

  • epochMax is assigned the value of the epochsP input, which sets the maximum number of epochs (iterations) of the algorithm.
  • epochCurrent is set to "0" to track the current epoch.

Set a fixed parameter: the value of "1.0" is assigned to the gamma_es variable. This parameter is used in the Firefly algorithm, which is part of the overall strategy.

The initial population of "a" solutions is initialized. For each solution in the population:

  • Each coordinate of the solution vector (a[i].c[c]) is initialized with a random value taken from the [rangeMin[c], rangeMax[c]] range.
  • The value of each coordinate is "rounded" to the nearest acceptable value, taking into account the (rangeStep[c]) step using the u.SeInDiSp method.
  • The objective function value (a[i].f and a[i].fB) for each solution is set to "-DBL_MAX".
//————————————————————————————————————————————————————————————————————
bool C_AO_ES::Init (const double &rangeMinP  [],
                    const double &rangeMaxP  [],
                    const double &rangeStepP [],
                    const int     epochsP = 0)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //------------------------------------------------------------------
  // Initialize arrays
  ArrayResize (levyStep, coords);

  // Initialize phase tracking
  inLocalSearchPhase = false;
  localSearchCenter  = 0;
  localSearchCounter = 0;

  // Initialize convergence tracking
  prevBestFitness   = -DBL_MAX;
  stagnationCounter = 0;

  // Initialize epoch tracking
  epochMax     = epochsP;
  epochCurrent = 0;

  // Fixed Firefly parameter
  gamma_es = 1.0;

  // Initialize the population randomly
  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]);
    }

    a [i].f  = -DBL_MAX;
    a [i].fB = -DBL_MAX;
  }

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

The Moving method implements the basic logic of the iterative optimization alternating between global search and local exploitation. Each method call increments the epoch counter to track the progress of the algorithm.

Handling search phases:
  • if the current phase is a global search, the space is explored using steps modeled on the Levy distribution, generating new solutions in the entire space in order to search for new areas with potential;

  • if an improvement is found after a global search — the algorithm switches to the local phase. In this case, the most promising solution (agent) is selected, around which a search will be conducted in order to refine it;

  • if improvements do not occur over several iterations (stagnation accumulates), the activity to search for new solutions increases through more aggressive flight steps, reducing the "lambda" parameter for a wider exploration of the space;

  • if the algorithm is in the local search phase, there is an 80% probability that local optimization will be performed using a method that simulates the Firefly algorithm. In this case, the selected solution is refined, and the local iteration counter is increased. Once the specified number of local-search iterations is reached, the algorithm returns to global search. 

Additional randomized perturbations with a certain probability of changing the decisions of the population is aimed at preserving diversity and preventing getting stuck in local "traps".

    Thus, the method implements a strategy of step-by-step and flexible search alternating between global exploration and targeted local optimization. This allows for a balance between exploring new areas and improving existing solutions.

    //————————————————————————————————————————————————————————————————————
    void C_AO_ES::Moving ()
    {
      epochCurrent++;
    
      // PHASE DECISION: Alternating between global and local search
      if (!inLocalSearchPhase)
      {
        // PHASE 1: GLOBAL EXPLORATION using Levy flights
        GlobalExploration ();
    
        // Check whether it is necessary to switch to local search
        // Switch if we find a promising area (improving the best fitness)
        if (fB > prevBestFitness)
        {
          inLocalSearchPhase = true;
          localSearchCounter = 0;
          prevBestFitness    = fB;
          stagnationCounter  = 0;
    
          // Find the best agent to center local search
          localSearchCenter = 0;
          double bestFit = -DBL_MAX;
    
          for (int i = 0; i < popSize; i++)
          {
            if (a [i].f > bestFit)
            {
              bestFit = a [i].f;
              localSearchCenter = i;
            }
          }
        }
        else
        {
          stagnationCounter++;
    
          // In case of stagnation, increase exploration
          if (stagnationCounter > 5)
          {
            lambda = MathMax (1.0, lambda - 0.1); // Make Levy flights more aggressive
          }
        }
      }
      else
      {
        if (u.RNDprobab () < 0.8)
        {
          // PHASE 2: LOCAL EXPLOITATION using the Firefly algorithm
          LocalExploitation ();
    
          localSearchCounter++;
    
          // Return to global search after local iterations are complete
          if (localSearchCounter >= localIterations)
          {
            inLocalSearchPhase = false;
            lambda = params [1].val; // Reset lambda to its original value
          }
        }
        else
        {
          for (int i = 0; i < popSize; i++)
          {
            for (int c = 0; c < coords; c++)
            {
              if (u.RNDprobab () < 0.5)
              {
                a [i].c [c] = cB [c];
              }
            }
          }
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The Revision method is designed to update information about the best solutions during the algorithm operation. The method goes through all elements of the current population of solutions and for each solution compares its current fitness with that stored in its personal best result. If the current value is better, then the personal best result and the corresponding solution are updated (the best solution of the current iteration is saved).

    The method then compares the fitness of each solution with the current global best value found across the entire population. If the current result is the best, the global result is updated and the corresponding solution is stored. Thus, the method maintains up-to-date information about local and global optimal solutions in the current state of the algorithm, ensuring that the best solutions found are preserved at each step.

    //————————————————————————————————————————————————————————————————————
    void C_AO_ES::Revision ()
    {
      for (int i = 0; i < popSize; i++)
      {
        // Updating the personal best one
        if (a [i].f > a [i].fB)
        {
          a [i].fB = a [i].f;
          ArrayCopy (a [i].cB, a [i].c, 0, 0, WHOLE_ARRAY);
        }
    
        // Update the global best one
        if (a [i].f > fB)
        {
          fB = a [i].f;
          ArrayCopy (cB, a [i].c, 0, 0, WHOLE_ARRAY);
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The GlobalExploration method implements a global exploration phase using Levy flights to explore the solution space. For each solution in the loop, a random step is generated across the entire population using the Levy distribution, which determines the direction and length of the movement in the solution space.

    The Levy distribution is characterized by "heavy tails", which allows for both small refinement steps and large, rare jumps to explore distant regions. In the loop, for each coordinate of the solution, the following is calculated:

    • search range (the difference between the maximum and minimum coordinate values),
    • stepScale adaptive scaling factor. It decreases as the search progresses (the closer to the end, the smaller the steps), helping to narrow the search area around promising solutions, whereas at the very beginning of the search the steps are larger for a broader exploration.
    • the Levy step is applied: the coordinate of the current solution is changed by an amount proportional to the Levy step, the search span, and the scale factor.
    • boundary correction: the new coordinate is checked to see if it is outside the allowed range; if so, the value is adjusted to remain within the specified range.
    Ultimately, the method updates the position of each solution in the population using steps derived from the Levy distribution, thus providing a global exploration of the solution space with adaptive control of the step size depending on the optimization progress. This allows us to explore the space at the beginning and gradually refine our results as we move towards the optimal solution.
    //————————————————————————————————————————————————————————————————————
    // PHASE 1: GLOBAL EXPLORATION using Levy flights
    void C_AO_ES::GlobalExploration ()
    {
      for (int i = 0; i < popSize; i++)
      {
        // Generate Levy step
        GenerateLevyStep ();
    
        // Update position using Levy flight
        for (int c = 0; c < coords; c++)
        {
          double range = rangeMax [c] - rangeMin [c];
    
          // Adaptive step size depending on search progress
          double progress = (epochMax > 0) ? (double)epochCurrent / (double)epochMax : 0.5;
          double stepScale = 0.01 + 0.2 * (1.0 - progress); // Start with big steps
    
          // Apply the Levy step
          a [i].c [c] += levyStep [c] * range * stepScale;
    
          // Boundary restrictions
          a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The LocalExploitation method implements a local improvement stage for solutions using the Firefly algorithm. At the initial stage, agents located inside a given hypersphere around the best solution are determined. To do this, the distance of each agent to the center of the hypersphere is calculated, and if it is less than the radius or the agent is the center of the search, it is included in the selected group.

    If there are too few agents inside the hypersphere, then the search area is expanded by including the nearest neighbors. To do this, the distance to the center is calculated for all agents, and the closest ones are selected - either by numerical quantity (for example, 5) or by population share (for example, 30%). These agents are added to the group.

    Firefly algorithm application scope: The selected agents interact according to the rules of the algorithm:
    • For each agent, it is checked whether there is another agent in the group with better fitness.
    • If there is one, the current agent moves towards a more attractive (better) agent, and the distance between them is calculated.
    • The more attractive another agent is, the greater its "luminosity" (depending on the distance and algorithm parameters).
    • The movement combines attraction toward a better agent with a random perturbation to preserve diversity.
    • After the movements, the coordinate boundaries are checked to ensure that the solutions remain within the acceptable range.

      As a result, the method allows for local improvement of solutions near the best points found, using the interaction of agents according to the Firefly algorithm strategy model: the best solutions attract the worst ones, facilitating the point optimization of the solution space.

      //————————————————————————————————————————————————————————————————————
      // PHASE 2: Local exploitation using the Firefly algorithm
      void C_AO_ES::LocalExploitation ()
      {
        // Identification of agents inside the hypersphere around the best solution
        double agents_in_sphere [];
        ArrayResize (agents_in_sphere, 0);
      
        for (int i = 0; i < popSize; i++)
        {
          double normalized_dist = 0.0;
      
          for (int c = 0; c < coords; c++)
          {
            double diff = (a [i].c [c] - a [localSearchCenter].c [c]) / (rangeMax [c] - rangeMin [c]);
            normalized_dist += diff * diff;
          }
          normalized_dist = MathSqrt (normalized_dist);
      
          // Include agents inside the sphere or the center itself
          if (normalized_dist <= sphereRadius || i == localSearchCenter)
          {
            int size = ArraySize (agents_in_sphere);
            ArrayResize (agents_in_sphere, size + 1);
            agents_in_sphere [size] = i;
          }
        }
      
        // If there are too few agents, expand to the nearest neighbors
        if (ArraySize (agents_in_sphere) < 5)
        {
          ArrayResize (agents_in_sphere, 0);
      
          // Calculate distances for all agents
          double distances [];
          ArrayResize (distances, popSize);
      
          for (int i = 0; i < popSize; i++)
          {
            if (i == localSearchCenter)
            {
              distances [i] = 0.0;
            }
            else
            {
              double dist = 0.0;
              for (int c = 0; c < coords; c++)
              {
                double diff = (a [i].c [c] - a [localSearchCenter].c [c]) / (rangeMax [c] - rangeMin [c]);
                dist += diff * diff;
              }
              distances [i] = MathSqrt (dist);
            }
          }
      
          // Take the closest 5 agents or 30% of the population
          int numAgents = MathMin (popSize, MathMax (5, popSize / 3));
          ArrayResize (agents_in_sphere, numAgents);
      
          // Simple selection of nearby agents
          for (int k = 0; k < numAgents; k++)
          {
            double minDist = DBL_MAX;
            int minIdx = -1;
      
            for (int i = 0; i < popSize; i++)
            {
              bool already_selected = false;
      
              for (int j = 0; j < k; j++)
              {
                if (agents_in_sphere [j] == i)
                {
                  already_selected = true;
                  break;
                }
              }
      
              if (!already_selected && distances [i] < minDist)
              {
                minDist = distances [i];
                minIdx = i;
              }
            }
      
            agents_in_sphere [k] = minIdx;
          }
        }
      
        // Execute the Firefly algorithm on the selected agents
        int numLocalAgents = ArraySize (agents_in_sphere);
      
        for (int i = 0; i < numLocalAgents; i++)
        {
          int idx_i = (int)agents_in_sphere [i];
      
          for (int j = 0; j < numLocalAgents; j++)
          {
            if (i == j) continue;
      
            int idx_j = (int)agents_in_sphere [j];
      
            // If agent j is better than agent i, move i to j
            if (a [idx_j].f > a [idx_i].f)
            {
              // Calculate the distance
              double r_squared = 0.0;
      
              for (int c = 0; c < coords; c++)
              {
                double diff = (a [idx_j].c [c] - a [idx_i].c [c]) / (rangeMax [c] - rangeMin [c]);
                r_squared += diff * diff;
              }
      
              // Calculate attractiveness
              double beta = beta0 * MathExp (-gamma_es * r_squared);
      
              // Move agent i to agent j
              for (int c = 0; c < coords; c++)
              {
                double range = rangeMax [c] - rangeMin [c];
      
                // Firefly motion equation
                a [idx_i].c [c] += beta * (a [idx_j].c [c] - a [idx_i].c [c]) +
                                   alpha * (u.RNDfromCI (-0.5, 0.5)) * range * 0.1;
      
                // Apply borders
                a [idx_i].c [c] = u.SeInDiSp (a [idx_i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
              }
            }
          }
        }
      }
      //————————————————————————————————————————————————————————————————————
      

      The GenerateLevyStep method is responsible for generating the Levy step required for the implementation of the global exploration strategy in the optimization algorithm. The method uses Mantegna algorithm to generate these steps, a method we have already discussed above.

      Calculating sigma:

      The formula in the code calculates the "sigma" parameter. This parameter is related to the scale of the Levy distribution and depends on the constant "lambda," which characterizes the shape of the Levy distribution (determines how "heavy" the distribution tails will be). Gamma() is the gamma function, a generalization of the factorial to real numbers (the code for this method is presented below). This parameter is needed to generate normally distributed values, which are then used in the Mantegna algorithm.

      The generation of the Levy step occurs independently for each coordinate (parameter) of the solution. Two random variables u_val and v_val are generated from a normal (Gaussian) distribution, the absolute value of v_val is taken. The Levy step "levyStep [c]" is calculated using the Mantegna algorithm formula: u_val / Math.pow(v_val, 1.0 / lambda). A check is performed to avoid division by zero (if v_val is very small). The Levy step is limited in absolute value. This is done to prevent excessively large jumps.

      As a result of the method, the levyStep array is generated, containing the Levy step values for each coordinate. These steps are then used in the GlobalExploration method to update the position of each solution in the search space.

      //————————————————————————————————————————————————————————————————————
      // Generate Levy step using Mantegna algorithm
      void C_AO_ES::GenerateLevyStep ()
      {
        // Calculate sigma for Mantegna algorithm
        double numerator   = Gamma (1.0 + lambda) * MathSin (M_PI * lambda / 2.0);
        double denominator = Gamma ((1.0 + lambda) / 2.0) * lambda * MathPow (2.0, (lambda - 1.0) / 2.0);
        double sigma = MathPow (numerator / denominator, 1.0 / lambda);
      
        for (int c = 0; c < coords; c++)
        {
          // Generate u and v from normal distributions
          double u_val = GenerateGaussian () * sigma;
          double v_val = MathAbs (GenerateGaussian ());
      
          // Calculate the Levy step
          if (v_val > 1e-10)
          {
            levyStep [c] = u_val / MathPow (v_val, 1.0 / lambda);
          }
          else
          {
            levyStep [c] = 0.0;
          }
      
          // Limit extreme values
          if (MathAbs (levyStep [c]) > 10.0)
          {
            levyStep [c] = 10.0 * (levyStep [c] > 0 ? 1.0 : -1.0);
          }
        }
      }
      //————————————————————————————————————————————————————————————————————
      

      The GenerateGaussian method implements the generation of random numbers that follow a normal (Gaussian) distribution with a mean of "0" and a standard deviation of "1". It uses the Box-Muller transform, a fairly common method for this problem, and one we have already used in previous articles.

      The static variables "hasSpare" and "spare" are used, the Box-Muller transform generates two independent random numbers from a normal distribution at a time, 'hasSpare' is a logical variable that determines whether one of the generated numbers is saved for the next function call, 'spare' is a variable that stores this "spare" number.

        Generate new random numbers (if necessary):

        If there is no "spare" number, then: two independent random variables u_val and v_val are generated from a uniform distribution on the interval (0, 1). The u.RNDfromCI function generates uniformly distributed numbers. The Box-Muller transformation is applied:

        • The "mag" (magnitude) is calculated as the square root of -2.0 * Math.log(u_val + 1e-10). Adding a small number to u_val is necessary to avoid taking the logarithm of zero, which is unacceptable.
        • The "spare" number is calculated as mag * Math.cos(2.0 * M_PI * v_val).
        • The value returned is mag * Math.sin(2.0 * M_PI * v_val).

        The method returns one of the generated random numbers that follows a normal distribution.
        //————————————————————————————————————————————————————————————————————
        // Generate a Gaussian random number using the Box-Muller transform
        double C_AO_ES::GenerateGaussian ()
        {
          static bool hasSpare = false;
          static double spare;
        
          if (hasSpare)
          {
            hasSpare = false;
            return spare;
          }
        
          hasSpare = true;
          double u_val = u.RNDfromCI (0.0, 1.0);
          double v_val = u.RNDfromCI (0.0, 1.0);
        
          double mag = MathSqrt (-2.0 * MathLog (u_val + 1e-10));
          spare = mag * MathCos (2.0 * M_PI * v_val);
        
          return mag * MathSin (2.0 * M_PI * v_val);
        }
        //————————————————————————————————————————————————————————————————————
        

        The Gamma method is a function that approximates the gamma function for a given argument of "z". Because the gamma function is an important mathematical function, especially in statistics and optimization, but its exact computation can be difficult and energy-consuming, approximations are often used. We use the Lanczos approximation, which provides good accuracy at relatively low computational cost. Let's discuss the main points.

        If the "z" argument is less than "0.5", the Euler reflection formula is applied. This allows the gamma function to be calculated for arguments close to zero. The reflection formula relates the gamma function of "z" to the gamma function of "1 - z". We will use fixed Lanczos coefficients, which were carefully chosen to ensure high approximation accuracy, as well as coefficients and a formula that include power and exponential functions. The method returns the approximate value of the gamma function for the given argument of "z".

        The Lanczos approximation provides good accuracy, sufficient for most practical applications. The algorithm is relatively efficient since it requires only a few arithmetic operations and table search of coefficients. It is suitable for a wide range of argument values, especially when combined with the reflection formula. In general, the Gamma method is a fairly accurate way to approximate the gamma function.

        //————————————————————————————————————————————————————————————————————
        // Approximation of the gamma function using Lanczos approximation
        double C_AO_ES::Gamma (double z)
        {
          if (z < 0.5)
          {
            // Reflection formula for z < 0.5
            return M_PI / (MathSin (M_PI * z) * Gamma (1.0 - z));
          }
        
          // Lanczos coefficients
          const double g = 7.0;
          double coef [] =
          {
            0.99999999999980993,
            676.5203681218851,
            -1259.1392167224028,
            771.32342877765313,
            -176.61502916214059,
            12.507343278686905,
            -0.13857109526572012,
            9.9843695780195716e-6,
            1.5056327351493116e-7
          };
        
          z -= 1.0;
          double x = coef [0];
        
          for (int i = 1; i < 9; i++)
          {
            x += coef [i] / (z + i);
          }
        
          double t = z + g + 0.5;
          double sqrt2pi = MathSqrt (2.0 * M_PI);
        
          return sqrt2pi * MathPow (t, z + 0.5) * MathExp (-t) * x;
        }
        //————————————————————————————————————————————————————————————————————
        


        Test results

        Although the algorithm failed to make it into our ranking table, its results are noteworthy.

        ES|Eagle Strategy|100.0|1.0|0.1|20.0|0.1|1.2|
        =============================
        5 Hilly's; Func runs: 10000; result: 0.7077570016043803
        25 Hilly's; Func runs: 10000; result: 0.4307775904381953
        500 Hilly's; Func runs: 10000; result: 0.2747545259235643
        =============================
        5 Forest's; Func runs: 10000; result: 0.7173720280531499
        25 Forest's; Func runs: 10000; result: 0.3448423301485268
        500 Forest's; Func runs: 10000; result: 0.1597390810339928
        =============================
        5 Megacity's; Func runs: 10000; result: 0.5184615384615384
        25 Megacity's; Func runs: 10000; result: 0.2775384615384615
        500 Megacity's; Func runs: 10000; result: 0.11063076923077016
        =============================
        All score: 3.54187 (39.35%)

        The visualization clearly shows how the search phases are divided according to strategies, when long throws and short refining moves occur.

        Hilly

        ES on the Hilly test function

        Forest

        ES on the Forest test function

        Megacity

        ES on the Megacity test function

        The ES algorithm is included in the rating table for reference.

        # 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)
        1 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
        2 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
        3 AMOm animal migration ptimization 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
        4 (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
        5 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
        6 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
        7 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
        8 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
        9 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
        10 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
        11 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
        12 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
        13 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
        14 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
        15 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
        16 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
        17 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
        18 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
        19 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
        20 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
        21 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
        22 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
        23 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
        24 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
        25 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
        26 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
        27 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
        28 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
        29 (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
        30 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
        31 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
        32 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
        33 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
        34 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
        35 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
        36 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
        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
        38 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
        39 SOA simple optimization algorithm 0.91520 0.46976 0.27089 1.65585 0.89675 0.37401 0.16984 1.44060 0.69538 0.28031 0.10852 1.08422 4.181 46.45
        40 ABHA artificial bee hive algorithm 0.84131 0.54227 0.26304 1.64663 0.87858 0.47779 0.17181 1.52818 0.50923 0.33877 0.10397 0.95197 4.127 45.85
        41 ACMO atmospheric cloud model optimization 0.90321 0.48546 0.30403 1.69270 0.80268 0.37857 0.19178 1.37303 0.62308 0.24400 0.10795 0.97503 4.041 44.90
        42 ADAMm adaptive moment estimation M 0.88635 0.44766 0.26613 1.60014 0.84497 0.38493 0.16889 1.39880 0.66154 0.27046 0.10594 1.03794 4.037 44.85
        43 CGO chaos game optimization 0.57256 0.37158 0.32018 1.26432 0.61176 0.61931 0.62161 1.85267 0.37538 0.21923 0.19028 0.78490 3.902 43.35
        44 CROm coral reefs optimization M 0.78512 0.46032 0.25958 1.50502 0.86688 0.35297 0.16267 1.38252 0.63231 0.26738 0.10734 1.00703 3.895 43.27
        45 ATAm artificial tribe algorithm M 0.71771 0.55304 0.25235 1.52310 0.82491 0.55904 0.20473 1.58867 0.44000 0.18615 0.09411 0.72026 3.832 42.58
        ES eagle_strategy_optimization 0.70776 0.43078 0.27475 1.41328 0.71737 0.34484 0.15973 1.22194 0.51846 0.27754 0.11063 0.90663 3.542 39.35
        RW neuroboids optimization algorithm 2(joo) 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


        Summary

        The ES algorithm performed averagely in the population optimization benchmark, scoring 39% of the maximum possible points, which prevented it from being included in the top 45 algorithms.

        Despite this, the algorithm is still worth considering because of its original two-phase search concept based on a biologically based model of eagle hunting behavior. The use of Levy flights for global exploration is a mathematically beautiful solution that has been theoretically proven to be optimal for random search problems. Adaptive implementation mechanisms, including dynamic switching between phases and automatic parameter adjustment during stagnation, show potential for improving the basic concept.

        The algorithm may find its niche in specific classes of problems where a balance between global exploration and local exploitation is important, especially in the presence of multiple local optima and noisy data. The integration of the firefly algorithm for local search creates an interesting synergy between two nature-inspired methods, although the overall performance indicates the need for further parameter optimization and possible modification of the underlying mechanisms.

        The ES algorithm can be considered as an instructive example of a hybrid approach to optimization and a basis for developing more advanced methods combining different metaheuristics. Its relatively simple implementation and intuitive logic make the algorithm suitable for research experiments in the field of evolutionary computation.

        tab

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

        chart

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

        ES pros and cons:

        Pros:

        1. Fast.

        Cons:

        1. A large number of parameters.

        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

        # Name Type Description
        1 #C_AO.mqh
        Include
        Parent class of population optimization algorithms
        2 #C_AO_enum.mqh
        Include
        Enumeration of population optimization algorithms
        3 TestFunctions.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
        Script The 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_ES.mq5
        Script ES test stand

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

        Attached files |
        ES.zip (224.37 KB)
        MQL5 Wizard Techniques you should know (Part 90): Fenwick Tree Money Management with 1D CNN in MQL5 MQL5 Wizard Techniques you should know (Part 90): Fenwick Tree Money Management with 1D CNN in MQL5
        This article implements a Fenwick Tree (Binary Indexed Tree) for volume-aware money management inside an MQL5 Wizard Expert Advisor. We structure cumulative volume in O(log n) and apply four scaling modes—linear, conservative, aggressive, and mean-reversion—optionally gated by a lightweight 1D CNN. Practical tests compare the algorithm alone versus the CNN‑filtered approach to illustrate adaptive lot sizing and risk control under varying volume topologies.
        The Power of MetaTrader 5: From Step-by-Step Debugging to EX5 Protection in a Unified Environment The Power of MetaTrader 5: From Step-by-Step Debugging to EX5 Protection in a Unified Environment
        This article examines a comprehensive approach to developing trading algorithms: from project setup and logic debugging to protecting the finished product. We will explore MetaEditor's built-in tools, including step-by-step debugging using real ticks, performance profiling, and direct integration with C++ DLLs to speed up calculations. The article also explains how to protect intellectual property using MQL5 Cloud Protector. The application of the described techniques will transform Expert Advisor development from a chaotic search for solutions into a systematic process, significantly reducing the time required to develop a strategy.
        MetaTrader 5 Machine Learning Blueprint (Part 16): Nested CV for Unbiased Evaluation MetaTrader 5 Machine Learning Blueprint (Part 16): Nested CV for Unbiased Evaluation
        The article presents a V-in-V nested cross-validation pipeline for financial data that breaks leakage at three decision points: hyperparameter search, calibration, and final evaluation. A temporal three‑zone split isolates an inner walk‑forward search with the 1‑SE rule from an outer walk‑forward or CPCV evaluation, while OOF isotonic calibration is fitted independently. The resulting UnifiedValidationCalibrator delivers unbiased out‑of‑sample scores and well‑calibrated probabilities for deployment.
        Beyond GARCH (Part II): Measuring the Fractal Dimension of Markets Beyond GARCH (Part II): Measuring the Fractal Dimension of Markets
        Building on the partition function analysis from Part 1, this article deepens the theoretical foundation before completing the analytical pipeline. We first give a full treatment of the Hurst exponent: what it measures, what it implies about market memory, and why it matters for the MMAR. This is followed by an intuitive exploration of multifractal spectra and what f(α) reveals about volatility heterogeneity. We then move to implementation: extracting the scaling function τ(q), estimating H via R/S analysis, and fitting the multifractal spectrum across four candidate distributions. By the end, we have the complete parameter set needed to construct the MMAR process in Part 3. Part 2 of an eight-part series.