Русский
preview
Elite Crystal Evolution Algorithm (CEO-inspired): Practical Implementation

Elite Crystal Evolution Algorithm (CEO-inspired): Practical Implementation

MetaTrader 5Trading |
434 0
Andrey Dik
Andrey Dik

Contents

  1. Implementation of the Algorithm
  2. Test Results
  3. Conclusions


Implementation of the Algorithm

We continue our discussion of the specific methods of the ECEA optimization algorithm, the concept of which was introduced in the previous article. Let me remind you that the developed algorithm operates on a population of crystals that are dynamically divided into two groups: elite (analogous to frozen crystals) and regular (analogous to unfrozen crystals). Elite crystals perform an intensive local search with an adaptively decreasing step size, ensuring exploitation of the identified promising regions. Regular crystals use three movement strategies with probabilities of 40–30–30 percent: directed movement toward the globally best solution (exploitation), movement toward the nearest elite agent and the center of mass of the elite group (moderate exploitation), and exploratory random jumps at two scales (exploration). A periodic "wind" effect regenerates the worst non-elite crystals with a probability of ten percent, ensuring population diversification. All right, let's keep going.

The MoveTowardsEliteCluster function is designed to move a non-elite agent (crystal) toward the group of best (elite) agents.

Finding the nearest elite agent:first, the FindNearestElite(idx) function is called to find the index of the elite agent closest to the current agent (with index idx). If there are no elite agents (nearestElite < 0), the function terminates, since there is nothing to move toward.

Movement along each dimension (coordinate):the method then iterates through all dimensions (coordinates "c") of the current agent. Two direction vectors are calculated:

  • toElite — the difference between the coordinate of the nearest elite agent and the agent's current coordinate; it points toward the nearest elite agent.
  • toCenter — the difference between the coordinate of the "center of mass" of all elite agents and the agent's current coordinate; it points to the averaged position of the elite agents.

Two random numbers, "r1" and "r2," are generated between 0.0 and 1.0. These numbers are used to weight the influence of each of the two directions (toward the nearest elite agent and toward the center of mass of the elite agents). The agent's current coordinate is updated by adding the following:

  • r1 * 0.3 * toElite — the portion of the movement directed toward the nearest elite agent. A fixed coefficient of 0.3 determines how strongly the agent will be drawn toward the nearest elite agent.
  • r2 * 0.2 * toCenter — the component of motion directed toward the center of mass of the elite agents. A fixed coefficient of 0.2 determines how strongly the agent will tend toward the average position of the elite agents.

After summing these components, the new coordinate value is passed through the "u.SeInDiSp" function. As before, this function ensures that the coordinate value remains within the allowed bounds and conforms to the specified step size.

This method helps non-elite agents move closer to areas where good solutions have already been found. The agent does not simply move in one direction; instead, it combines movement toward the nearest strong solution with movement toward the common "center of attraction" of all strong solutions. The random weighting coefficients "r1"and "r2"add an element of stochasticity to help avoid getting stuck in the same place.

//————————————————————————————————————————————————————————————————————
void C_AO_ECEA::MoveTowardsEliteCluster (int idx)
{
  int nearestElite = FindNearestElite (idx);
  if (nearestElite < 0) return;

  for (int c = 0; c < coords; c++)
  {
    // Movement toward the nearest elite agent
    double toElite = a [nearestElite].c [c] - a [idx].c [c];
    // Movement toward the center of mass of elite agents
    double toCenter = centerMass [c] - a [idx].c [c];

    double r1 = u.RNDfromCI (0.0, 1.0);
    double r2 = u.RNDfromCI (0.0, 1.0);

    a [idx].c [c] = a [idx].c [c] + r1 * 0.3 * toElite + r2 * 0.2 * toCenter;
    a [idx].c [c] = u.SeInDiSp (a [idx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
//————————————————————————————————————————————————————————————————————

The ExploratoryMove procedure is designed to move an agent randomly in order to explore the solution space.

The agent with index "idx" will make a random move. The method iterates over all dimensions (coordinates "c") of the current agent, and for each coordinate, it calculates "range" — the full range of valid values for that coordinate, from the minimum (rangeMin[c]) to the maximum (rangeMax[c]). A random number "r" is generated in the range from 0.0 to 1.0. This number determines what type of exploration will be conducted:

  • If "r" is less than 0.7 (70% of the time), the agent takes a small step that promotes local exploration of a particular region of the solution space. The magnitude of this step is determined as a random value from -1.0 to 1.0, multiplied by the full "range", by the "explorationRate" coefficient (which controls the overall exploration intensity), and by 0.1 (to keep the step relatively small).
  • Otherwise (30% of the time), the agent takes a large leap, which facilitates global exploration of large regions of the solution space. The size of this step is determined in the same way, but is multiplied by 0.5 (which makes the jump significantly larger than during local exploration).

The current value of the agent's coordinate (a [idx].c [c]) is updated by adding the calculated "move", and is then passed through the u.SeInDiSp function.

This method introduces an element of randomness into the agent's behavior, allowing it to leave areas that have already been explored and find new, potentially better solutions. The procedure ensures balanced exploration: in most cases, the agent takes small steps to fine-tune solutions close to the current ones, but periodically takes large leaps to cover broader regions of the search space. The explorationRate coefficient allows controlling the overall activity of exploration moves.

//————————————————————————————————————————————————————————————————————
void C_AO_ECEA::ExploratoryMove (int idx)
{
  for (int c = 0; c < coords; c++)
  {
    double range = rangeMax [c] - rangeMin [c];

    // A simple yet effective method for exploration
    double r = u.RNDfromCI (0.0, 1.0);

    double move;
    if (r < 0.7)
    {
      // 70%: small steps (local exploration)
      move = u.RNDfromCI (-1.0, 1.0) * range * explorationRate * 0.1;
    }
    else
    {
      // 30%: large leaps (global exploration)
      move = u.RNDfromCI (-1.0, 1.0) * range * explorationRate * 0.5;
    }

    a [idx].c [c] = a [idx].c [c] + move;
    a [idx].c [c] = u.SeInDiSp (a [idx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
//————————————————————————————————————————————————————————————————————

The following method describes the "wind" effect, which is a mechanism for moving the worst non-elite agent (crystal) in order to update its position and potentially find a better solution.

Search for the worst non-elite agent:the variable worstNonElite is initialized to -1 (the worst agent has not yet been found), and worstFitness is initialized to the maximum possible value (DBL_MAX). The function iterates over all agents in the population of popSize. For each agent, the function checks whether it is non-elite and whether its fitness (a [i].f) is worse than the current worstFitness value. If both conditions are met, then that agent becomes the new "worst" (the worstFitness and worstNonElite values are updated).

Checking for the worst agent:if worstNonElite remains -1, this means that either there are no non-elite agents, or they all have equally "good" (or "bad," depending on the interpretation) fitness, making it impossible to identify the worst one. In this case, the function terminates.

Movement of the worst agent:a random "strategy" number is generated in the range from 0.0 to 1.0, which determines exactly how the worst agent will move.

If strategy is less than 0.5 (50% of the time),the agent moves near the best solution. For each coordinate "c":

  • the range for the given coordinate is calculated,
  • a small amount of random "noise" is generated within ±30% of the full range,
  • the agent's new position is set to (cB [c] + noise),
  • the coordinate value is forcibly restricted to the allowable minimum (rangeMin [c]) and maximum (rangeMax [c]) values,
  • The final adjustment of the coordinate value is performed using u.SeInDiSp to ensure that it matches the rangeStep [c] step size and the boundaries.

Otherwise (in 50% of cases),the agent moves to a completely random location within the allowable boundaries. For each coordinate "c":

  • a random number "xi" between 0.0 and 1.0 is generated,
  • the new position is set as: rangeMin [c] + xi * (rangeMax [c] - rangeMin [c]) — this ensures that the value is uniformly distributed between the minimum and the maximum,
  • the final correction of the coordinate value is performed using u.SeInDiSp.

The "wind" mechanism aims to "shake up" the system by removing the least successful non-elite agents and placing them in new, potentially more promising positions. This helps prevent stagnation, where the population consists mainly of similar, relatively ineffective solutions. A move can be either local, near the best solution found so far, or completely random, giving the algorithm a chance to discover an entirely new region of the search space.

//————————————————————————————————————————————————————————————————————
void C_AO_ECEA::WindEffect ()
{
  // The "wind" destroys the worst non-elite agents and moves them
  int worstNonElite = -1;
  double worstFitness = DBL_MAX;

  for (int i = 0; i < popSize; i++)
  {
    if (!crystalData [i].isElite && a [i].f < worstFitness)
    {
      worstFitness  = a [i].f;
      worstNonElite = i;
    }
  }

  if (worstNonElite < 0) return;

  // Move to a new location
  double strategy = u.RNDfromCI (0.0, 1.0);

  if (strategy < 0.5)
  {
    // Near the best solution
    for (int c = 0; c < coords; c++)
    {
      double range = rangeMax [c] - rangeMin [c];
      double noise = u.RNDfromCI (-1.0, 1.0) * range * 0.3;
      a [worstNonElite].c [c] = cB [c] + noise;

      if (a [worstNonElite].c [c] < rangeMin [c]) a [worstNonElite].c [c] = rangeMin [c];
      if (a [worstNonElite].c [c] > rangeMax [c]) a [worstNonElite].c [c] = rangeMax [c];
      a [worstNonElite].c [c] = u.SeInDiSp (a [worstNonElite].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
  else
  {
    // Completely randomly
    for (int c = 0; c < coords; c++)
    {
      double xi = u.RNDfromCI (0.0, 1.0);
      a [worstNonElite].c [c] = rangeMin [c] + xi * (rangeMax [c] - rangeMin [c]);
      a [worstNonElite].c [c] = u.SeInDiSp (a [worstNonElite].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
//————————————————————————————————————————————————————————————————————

The FindNearestElite function finds the index of the "elite" agent (crystal) closest to the specified agent. The agent index "idx" specifies the agent for which we are searching for the nearest elite neighbor. The following variables are initialized:

  • nearest — set to -1 (which means that the nearest elite agent has not yet been found);
  • minDist — initialized to the maximum possible value (DBL_MAX), so that any distance found will be smaller.
The function iterates over all agents in the population (popSize). For each agent "i" in the loop, the following conditions are checked:
  • Ignoring the agent itself: if "i" equals "idx" (that is, if this is the same agent for which we are searching for a neighbor), then this agent is skipped (continue).
  • Elite check: if agent "i" is not an elite agent, it is skipped. We search only among elite agents.

If agent "i" is an elite agent and is not "idx", the Euclidean distance between agent "idx" and agent "i" is calculated. For each coordinate "c", the difference (diff) between the corresponding coordinates of the agents is calculated. The square of this difference (diff * diff) is added to the total sum of squared differences, "dist". After iterating through all coordinates, the square root operation is applied to "dist" to obtain the actual Euclidean distance.

If the calculated distance "dist" is less than the current minimum distance "minDist", then we have found a new nearest elite agent. "minDist" (the current minimum distance) and "nearest" (the index of this new nearest elite agent) are updated. After iterating through all agents, the function returns the index (nearest) of the nearest elite agent. If no elite agents are found in the population, the function returns -1.

This function is auxiliary and is used to determine which "elite" solution (the most successful agent) another agent should approach.

//————————————————————————————————————————————————————————————————————
int C_AO_ECEA::FindNearestElite (int idx)
{
  int nearest = -1;
  double minDist = DBL_MAX;

  for (int i = 0; i < popSize; i++)
  {
    if (i == idx) continue;
    if (!crystalData [i].isElite) continue;

    double dist = 0.0;
    for (int c = 0; c < coords; c++)
    {
      double diff = a [idx].c [c] - a [i].c [c];
      dist += diff * diff;
    }
    dist = MathSqrt (dist);

    if (dist < minDist)
    {
      minDist = dist;
      nearest = i;
    }
  }

  return nearest;
}
//————————————————————————————————————————————————————————————————————

The CalculateEliteRadii function calculates the radius of influence for each "elite" agent (crystal) based on the distances to other elite agents. The function iterates through all agents in the population; only those agents marked as elite are processed.

Search for the nearest other elite agent. For each elite agent "i", the variable minDistToOtherElite is initialized to the maximum possible value (DBL_MAX) in order to find the minimum distance. A nested loop begins, which again iterates through all agents "j" in the population. We check that we are not comparing an agent with itself (i == j) and that the other agent "j" is also an elite agent (crystalData[j].isElite). The Euclidean distance between elite agent "i" and another elite agent "j" is calculated across all coordinates. If the calculated distance "dist" is less than the current minimum distance minDistToOtherElite, then minDistToOtherElite is updated.

Radius calculation and storage. After the inner loop (in which the minimum distance to another elite agent was found), the radius for the current elite agent "i" is calculated as half of that minimum distance (minDistToOtherElite/2.0). This calculated radius is stored in the data structure associated with agent "i" (crystalData[i].radius).

This mechanism is designed to determine the "region of influence" of each elite solution. The radius is chosen so that it is less than half the distance to the nearest neighbor. This means that the regions of influence of different elite agents should not overlap. This can be useful for maintaining diversity in the population, ensuring that different elite solutions truly represent separate, distinct search regions.

//————————————————————————————————————————————————————————————————————
void C_AO_ECEA::CalculateEliteRadii ()
{
  // Calculate the radii of influence of elite agents based on the distances between them
  for (int i = 0; i < popSize; i++)
  {
    if (!crystalData [i].isElite) continue;

    double minDistToOtherElite = DBL_MAX;

    for (int j = 0; j < popSize; j++)
    {
      if (i == j) continue;
      if (!crystalData [j].isElite) continue;

      double dist = 0.0;
      for (int c = 0; c < coords; c++)
      {
        double diff = a [i].c [c] - a [j].c [c];
        dist += diff * diff;
      }
      dist = MathSqrt (dist);

      if (dist < minDistToOtherElite)
      {
        minDistToOtherElite = dist;
      }
    }

    crystalData [i].radius = minDistToOtherElite / 2.0;
  }
}
//————————————————————————————————————————————————————————————————————

The GetDynamicExplorationRate function dynamically adjusts the exploration rate based on the algorithm's current progress. The main purpose of this function is to reduce the exploration intensity ("exploration rate") as the algorithm runs. In the early stages of the algorithm, it is important to explore new areas, and as it gets closer to an optimal solution, the focus shifts to more precise fine-tuning of the solutions already found.

The "progress" variable (execution progress) is calculated. It is defined as the ratio of the current number of iterations (iterationCount) to a fixed maximum value (100.0). If "progress" exceeds 1.0 (that is, more than 100 iterations have been completed), it is clipped to 1.0 to ensure that the maximum reduction factor is 0.7. The return value is the result of multiplying the base exploration rate (explorationRate) by the reduction factor (1.0 - 0.7 * progress).

In the early stages, "progress" is close to 0. As "progress" increases (as "progress" approaches 1.0), the coefficient will decrease. For example, when "progress" is equal to 1.0, the coefficient will be 1.0 - 0.7 * 1.0 = 0.3. This means that the exploration rate will be reduced to 30% of its base value.

Imagine that you have a "knob" for adjusting exploration. This knob is set to its maximum value at the beginning of the algorithm. The GetDynamicExplorationRate function gradually turns this knob, reducing the exploration intensity. Thus, in the later stages, the algorithm "wanders" less and focuses more on improving the solutions it has found.

//————————————————————————————————————————————————————————————————————
double C_AO_ECEA::GetDynamicExplorationRate ()
{
  // Adaptive exploration intensity
  // Start high and gradually reduce it over time
  double progress = (double)iterationCount / 100.0;
  if (progress > 1.0) progress = 1.0;

  return explorationRate * (1.0 - 0.7 * progress);
}
//————————————————————————————————————————————————————————————————————

The Revision method updates information about the best solution found and tracks stagnation (lack of improvement). Before a possible update, the current best function value (denoted by fB) is stored in the variable prevBest. This is necessary for subsequent comparison. Another function (UpdateEliteStatus) is called, which reviews and, if necessary, changes the "elite" status of agents in the population.

Updating the best solution. The use of [0] implies that agents are sorted or processed in such a way that a [0] always contains the current best solution. The function value for the best agent (a [0].f) is assigned to fB. The coordinates of this best solution, a [0].c, are copied to cB (the variable used to store the coordinates of the best-known solution).

Monitoring stagnation. The current best function value (fB) is compared with the previously saved best value (prevBest). If the difference between them is very small (less than 1e-10, indicating no significant improvement), the stagnation counter (noImprovementCount) is incremented by one. If, however, an improvement has occurred (the difference is greater than 1e-10), the stagnation counter is reset to zero.

This function performs a periodic "revision" of the algorithm's state:

  • determines which agents remain "elite";
  • records the best solution found so far;
  • checks whether the algorithm continues to find new, better solutions, or whether it has "stalled" (stagnation).
//————————————————————————————————————————————————————————————————————
void C_AO_ECEA::Revision ()
{
  double prevBest = fB;

  // Update the status of elite crystals
  UpdateEliteStatus ();
  fB = a [0].f;
  ArrayCopy (cB, a [0].c, 0, 0, coords);

  // Track stagnation
  if (MathAbs (fB - prevBest) < 1e-10) noImprovementCount++;
  else                                 noImprovementCount = 0;
}
//————————————————————————————————————————————————————————————————————


Test Results

The ECEA algorithm was tested on a standard benchmark comprising three types of functions (Hilly, Forest, Megacity) with different problem dimensions (five, twenty-five, and five hundred dimensions), with a budget of 10,000 objective function evaluations per run. Each test was repeated ten times to ensure statistical reliability. The overall ECEA result was 36 percent, which exceeds the baseline random search (26 percent) but falls short of the 45 percent threshold typical of top-performing optimization algorithms.

ECEA|Elite Crystal Evolution Algorithm|50.0|10.0|0.5|0.1|
=============================
5 Hilly's; Func runs: 10000; result: 0.6824547309245615
25 Hilly's; Func runs: 10000; result: 0.4467067359108001
500 Hilly's; Func runs: 10000; result: 0.29066242412735266
=============================
5 Forest's; Func runs: 10000; result: 0.6110808506476546
25 Forest's; Func runs: 10000; result: 0.36838637212724284
500 Forest's; Func runs: 10000; result: 0.20596535563655824
=============================
5 Megacity's; Func runs: 10000; result: 0.36
25 Megacity's; Func runs: 10000; result: 0.2033846153846154
500 Megacity's; Func runs: 10000; result: 0.11233846153846264
=============================
All score: 3.28098 (36.46%)

Crystallization structures are visible in the visualization; the algorithm's search capabilities are more pronounced on medium-dimensional functions.

Hilly

ECEA on the Hilly test function

Forest

ECEA on the Forest test function

Megacity

ECEA on the Megacity test function

Let's also take a look at how the ECEA algorithm performs on standard functions that are not included in our ranking of population-based algorithms but are commonly used for testing. As you can see, the algorithm shows very high convergence on these types of problems; therefore, each algorithm should be tested on various types of functions to determine the conditions under which its full potential is best realized.

Ackley

ECEA on the standard Ackley test function

Paraboloid

ECEA on the standard Paraboloid test function

The ECEA algorithm is included in the ranking table for reference.

No. 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) 1,000 p (500 F)
1 DOAdingom dingo_optimization_algorithm_M 0.47968 0.45367 0.46369 1.39704 0.94145 0.87909 0.91454 2.73508 0.78615 0.86061 0.84805 2.49481 6.627 73.63
2 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
3 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
4 AMOm animal migration optimization M 0.90358 0.84317 0.46284 2.20959 0.99001 0.92436 0.46598 2.38034 0.56769 0.59132 0.23773 1.39675 5.987 66.52
5 (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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 EOm extremal_optimization_M 0.76166 0.77242 0.31747 1.85155 0.99999 0.76751 0.23527 2.00277 0.74769 0.53969 0.14249 1.42987 5.284 58.71
14 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
15 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
16 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
17 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
18 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
19 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
20 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
21 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
22 BSA backtracking_search_algorithm 0.97309 0.54534 0.29098 1.80941 0.99999 0.58543 0.21747 1.80289 0.84769 0.36953 0.12978 1.34700 4.959 55.10
23 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
24 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
25 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
26 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
27 DOA dream_optimization_algorithm 0.85556 0.70085 0.37280 1.92921 0.73421 0.48905 0.24147 1.46473 0.77231 0.47354 0.18561 1.43146 4.825 53.62
28 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
29 DEA dolphin_echolocation_algorithm 0.75995 0.67572 0.34171 1.77738 0.89582 0.64223 0.23941 1.77746 0.61538 0.44031 0.15115 1.20684 4.762 52.91
30 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
31 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
32 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
33 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
34 (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
35 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
36 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
37 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
38 WOAm whale 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
39 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
40 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
41 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
42 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
43 CMAES covariance_matrix_adaptation_evolution_strategy 0.76258 0.72089 0.00000 1.48347 0.82056 0.79616 0.00000 1.61672 0.75846 0.49077 0.00000 1.24923 4.349 48.33
44 DA_duelist duelist_algorithm 0.92782 0.53778 0.27792 1.74352 0.86957 0.47536 0.18193 1.52686 0.62153 0.33569 0.11715 1.07437 4.345 48.28
45 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
ECEA elite crystal evolution algorithm 0.68245 0.44671 0.29066 1.41982 0.61108 0.36838 0.20597 1.18543 0.36000 0.20338 0.11233 0.67571 3.281 36.46
RW random walk 0.48754 0.32159 0.25781 1.06694 0.37554 0.21944 0.15877 0.75375 0.27969 0.14917 0.09847 0.52734 2.348 26.09


Conclusions

The ECEA algorithm represents an attempt to adapt the crystallization concept from the original Crystal Energy Optimizer algorithm — developed for combinatorial problems — to the field of continuous optimization in multidimensional real-valued spaces. Experimental evaluation on our benchmark functions showed that the developed algorithm achieves a score of 36%, outperforming random search. This result indicates that the algorithm does indeed work and possesses meaningful search capabilities that surpass those of primitive methods; however, it does not reach the threshold required to be ranked among the best optimization algorithms.

An analysis of the reasons behind these results highlights the difficulty of directly adapting algorithms from the combinatorial domain to the continuous domain. The original CEO is based on specific mechanisms that make sense only for discrete structures, such as the graph of connections between route solutions in the traveling salesman problem, physical heat-transfer formulas in which the influence of the lake center is inversely proportional to distance, interaction between pairs of connected frozen crystals via triangular distances, and the process of depositing new crystals at positions between existing neighbors, with existing bonds being broken and new ones formed. In the process of adapting it into ECEA, all of these unique mechanisms were radically simplified, since they have no direct analogues in the geometry of continuous space. Ultimately, only the high-level conceptual ideas from the original algorithm remained: the division of the population into elite and ordinary agents, the presence of a central attractor, periodic diversification through the wind effect, and the balance between exploration and exploitation.

Despite the limitations, the work on the development of ECEA has certain scientific and practical value. The developed implementation is fully functional, stable, free of obvious implementation errors, and can be used as a baseline version for further exploration and improvement. The simplicity of the algorithm — with just four configurable parameters — makes it easy to understand and modify, which is useful for educational purposes and for quickly prototyping ideas.

There are several avenues for improving the algorithm. Incorporating an opposition-based learning mechanism — where, for each crystal, its opposite point in the search space is also considered — can help avoid situations where the entire population becomes concentrated in the wrong region. Periodic low-probability mutation of elite crystals can prevent them from getting stuck in local optima.

In conclusion, it should be noted that the experiment in adapting CEO for continuous optimization can be considered successful from a scientific research perspective, as it identified and demonstrated the key challenges of such adaptation and showed that the direct transfer of combinatorial algorithms to the continuous domain requires not merely the replacement of operators, but a rethinking of the underlying mechanisms. From a practical standpoint, the result is intermediate: the algorithm operates stably and outperforms random search, but does not match the performance of the best population-based optimization methods.

ECEA can be recommended as a teaching example of how to adapt nature-inspired algorithms, as a starting point for further improvements, or as a simple and straightforward method. The main contribution of this work lies not so much in the development of a highly efficient algorithm as in the systematic investigation of the process of cross-domain adaptation of metaheuristics.

tab

Figure 2. Color coding of algorithms for the corresponding tests

chart

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

Pros and Cons of theECEA Algorithm:

Pros:

  1. Fast.
  2. Best results on medium- and high-dimensional functions.

Cons:

  1. Weaker results on low-dimensional functions.

An archive containing the latest versions of the source code for the algorithms is attached to the article. The author of this article does not guarantee the absolute accuracy of the descriptions of the canonical algorithms; changes have been made to many of them to improve their search capabilities. The conclusions and judgments presented in the articles are based on the results of the experiments conducted.


Programs used in the article

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

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

Attached files |
ECEA.zip (297.47 KB)
Building Volatility Models in MQL5 (Part V): Implementing EGARCH as an Alternate Asymmetric Volatility Process Building Volatility Models in MQL5 (Part V): Implementing EGARCH as an Alternate Asymmetric Volatility Process
EGARCH models log-variance, avoiding the non-negativity constraints that can distort GARCH estimates and enabling a clear treatment of leverage asymmetry. The article provides a complete MQL5 implementation with logarithmic backcasting, simulation-based multi-step forecasting, and diagnostics including the Engle–Ng Sign Bias, Leverage Correlation, and Volatility Runs tests. Practical outputs include EGARCH Volatility, an Innovation Z-Score, and an Asymmetric Volatility Regime Oscillator to support regime analysis and strategy design.
Feature Engineering for ML (Part 12): Fractal Features in MQL5 Feature Engineering for ML (Part 12): Fractal Features in MQL5
A direct MQL5 port of the fractal detector writes each pattern at its center bar, so a buffer read by an expert advisor holds a value that only existed n bars later. We implement CFractalFeatures.mqh with ProcessBar for bar-by-bar use and Compute for full-series recalculation, covering detection, strength scored against a fixed or volatility-scaled floor, an event-based support/resistance ring, and trend-filtered breakout signals. Output is eighteen buffers published at the confirmation bar, verified against the Python reference to within 1e-13.
A Trailing Stop Engine in MQL5 Supporting Five Trail Methods Simultaneously A Trailing Stop Engine in MQL5 Supporting Five Trail Methods Simultaneously
We implement CTrailingEngine, an interface-driven MQL5 engine that evaluates each registered position on every tick and applies one of five trailing methods: fixed-pip, ATR multiplier, Parabolic SAR, percentage-of-profit, or swing high/low. All methods share the ITrailMethod contract, so new trails plug in without engine edits. Strict improvement and a one-point guard block backward moves and no-change SLTP modifications.
Price Action Analysis Toolkit Development (Part 78): Extending the Indicator Search Panel with Symbol Selection in MQL5 Price Action Analysis Toolkit Development (Part 78): Extending the Indicator Search Panel with Symbol Selection in MQL5
We continue enhancing our modular indicator search panel by adding symbol selection capabilities. The implementation allows users to search for built-in indicators, choose a destination symbol, and attach the selected indicator without opening multiple charts or running separate Expert Advisor instances.