Eagle Strategy (ES)
Contents
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.

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
σ = [Γ(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.
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.
//———————————————————————————————————————————————————————————————————— // 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).
//———————————————————————————————————————————————————————————————————— // 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.=============================
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.

ES on the Hilly test function

ES on the Forest test function

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.

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

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:
- Fast.
Cons:
- 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
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
MQL5 Wizard Techniques you should know (Part 90): Fenwick Tree Money Management with 1D CNN in MQL5
The Power of MetaTrader 5: From Step-by-Step Debugging to EX5 Protection in a Unified Environment
MetaTrader 5 Machine Learning Blueprint (Part 16): Nested CV for Unbiased Evaluation
Beyond GARCH (Part II): Measuring the Fractal Dimension of Markets
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use