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

Elite Crystal Evolution Algorithm (CEO-inspired): Theory

MetaTrader 5Trading systems |
177 0
Andrey Dik
Andrey Dik

Contents

  1. Introduction
  2. Implementation of the Algorithm
  3. Conclusion


Introduction

The article presents an adaptation of the Crystal Energy Optimizer (CEO) algorithm for continuous optimization problems. The algorithm is based on the physical process of a lake freezing, simulating the behavior of ice crystals as water transitions from a liquid to a solid state. The original CEO was developed for combinatorial problems (TSP), uses a graph of connections between crystals, and was introduced in a 2016 publication.

The Elite Crystal Evolution Algorithm (ECEA) is a population-based optimization algorithm I developed, inspired by the process of water crystallization as a lake freezes and by the concept behind the CEO algorithm. Imagine a lake in winter: when the temperature drops, the water begins to freeze, forming ice crystals. The best crystals (those in optimal conditions) become "frozen" and stable, while the rest continue to move and search for better positions. This natural metaphor forms the basis of our algorithm, which has been adapted to solve continuous optimization problems in multidimensional space.


Implementation of the Algorithm

The ECEA algorithm works with a population of "crystals" — points in the search space, each of which represents a potential solution to the problem. At the start of the process, all crystals are randomly distributed throughout the search space. After calculating the quality of each solution (fitness), the algorithm divides the population into two groups: elite crystals (the top ten solutions) and ordinary crystals (the remaining forty). This division is dynamic — at each iteration, a crystal's status may change depending on its current quality.

Elite crystals are analogous to the "frozen" crystals from the original natural metaphor. They have already identified promising areas within the search space and are now conducting a detailed local exploration of their neighborhood, taking small random steps around their current position. The size of these steps gradually decreases as the algorithm progresses — at first, elite crystals can explore a wider area, but with each iteration, their movements become increasingly cautious and precise, allowing for the discovery of higher-quality solutions within the promising areas already identified.

Ordinary, non-elite crystals behave quite differently. They represent "unfrozen" particles that are still actively searching for the best regions of the search space. For each such crystal, one of three movement strategies is randomly selected at each iteration to strike a balance between exploring new areas and exploiting information that has already been found.

The first strategy (triggered in 40% of cases) causes the crystal to move toward the global best solution found so far — this is analogous to being drawn toward the center of a lake, where conditions are most favorable. The attraction strength is adaptive: at the start of the algorithm, it is about 30% of the distance to the best solution, but as the algorithm progresses, it increases to 70%, which enhances the exploitation of the good regions that have been found.

The second strategy (30% of cases) involves moving toward the nearest elite crystal and, at the same time, toward the center of mass of all elite crystals. This simulates the influence of already "frozen" crystals on unfrozen ones — imagine the frozen parts of the lake radiating cold and drawing surrounding water molecules toward them. The crystal calculates where its nearest elite "neighbor" is located and also determines the center of mass of the entire elite group (the average point among all elite crystals), after which it takes a step that accounts for both of these directions with weights of 30 and 20 percent, respectively, modulated by random coefficients to introduce diversity.

The third strategy (the remaining 30% of cases) is an exploratory movement, analogous to the Brownian motion of molecules in a liquid phase. Here, the crystal takes a random step; with a 70% probability, this is a small step (10% of the search space size, scaled by the current exploration rate), and with a 30% probability, it is a large jump (50% of the search space size). The combination of small and large steps makes it possible to explore local areas while also making long jumps to discover new promising zones.

An important mechanism of the algorithm is the "Wind Effect," which is triggered randomly with a 10% probability at each iteration. This mechanism is inspired by a natural phenomenon in which the wind over a freezing lake breaks down fragile crystalline structures and carries the fragments to new locations. In the algorithm, the wind finds the worst of the non-elite crystals (the one with the worst fitness value) and moves it to a new location. With a 50/50 probability, the crystal is placed near the global best solution found so far (with a spread of plus or minus 30% of the search space size); otherwise, it is placed at a completely random point in the search space. This mechanism serves to diversify the population in order to avoid premature convergence to local extrema.

The ECEA algorithm uses adaptive control of exploration intensity. The exploration rate parameter starts at 0.5 (set by the user) and gradually decreases as the algorithm runs. Specifically, by the end of the first 100 iterations, the parameter is reduced by 70% of its initial value, marking a transition from active global search to more conservative exploitation of the solutions found. This adaptation occurs gradually and affects both the step sizes of elite crystals during local search and the amplitude of the exploratory movements of ordinary crystals.

At each iteration, the algorithm calculates the center of mass of the elite group — the average point of all elite crystals along each coordinate of the space. This point serves as the "center of the lake" in the natural analogy and is used as one of the attractors for the movement of non-elite crystals. In addition, radii of influence are calculated for each elite crystal, defined as half the distance to the nearest other elite crystal.

The algorithm runs iteratively: at each step, all crystals are first sorted by the quality of their solutions, and elite status is updated (the top ten are granted elite status, while the rest lose it); then the center of mass of the elite group is calculated, after which the appropriate movement strategy is applied to each crystal (local search for elite crystals or one of three strategies for ordinary crystals); the Wind Effect is applied with a certain probability; and finally, new fitness values are calculated and the global best solution and global worst solution found so far are updated. The algorithm continues to run until the specified number of iterations is reached.

As a result, from the original Crystal Energy Optimizer algorithm, developed for combinatorial problems such as the traveling salesman problem, we inherited several key conceptual ideas and adapted them for continuous optimization. So what did we replace, and with what?

The concept of frozen and unfrozen crystals was retained as a division of the population into elite and ordinary crystals. In the original algorithm, the crystals physically “froze” upon reaching a certain energy threshold, forming stable structures. We replaced the physical process of freezing with a simpler mechanism for ranking by solution quality: the best crystals become elite (the analogue of frozen crystals), while the rest remain in active search (the analogue of unfrozen crystals).

The idea of the center of the lake as an attractor evolved into the use of the global best solution and the center of mass of elite crystals. In the original CEO, the center of the lake was a source of energy that attracted unfrozen crystals using a physical heat-transfer formula, with strength inversely proportional to distance. We simplified this idea by using direct movement toward the best solution with an adaptive attraction strength, which is conceptually similar but avoids complex physical calculations.

The Wind-Blow mechanism was retained as a method of periodic population diversification. In the original algorithm, the wind broke the bonds between frozen crystals and moved selected crystals to new positions. Since our adaptation has no explicit link structure (graph), we implemented the wind as a mechanism for regenerating the worst non-elite crystals by placing them in new positions, either near the best solution or at random.

The balance between exploration and exploitation is achieved through three movement strategies and adaptive parameters. The original CEO used a complex system of influences from the center of the lake, frozen crystals, and the wind, combined into a formula for the total influence using lambda weighting coefficients. We replaced the physical formulas with three conceptually similar strategies: movement toward the best solution (analogous to the influence of the center), movement toward elite crystals (analogous to the influence of frozen crystals), and exploratory movement (analogous to random perturbations).

Local search for elite solutions was inherited from the behavior of frozen crystals in the original algorithm. In the CEO algorithm, frozen crystals continued to “grow” slowly, making micromovements to improve their positions. We implemented this through small random steps around the current positions of elite crystals, with a gradually decreasing amplitude. This makes it possible to fine-tune the good solutions found without losing them during the global search process.

It is important to note that we did not retain the key elements of the original that are specific to combinatorial optimization: the structure of the links between crystals (a graph), the formula for the influence of frozen crystals via the distances to pairs of connected neighbors, the physical heat-transfer formulas, the deposition process involving the selection of the optimal position between two frozen neighbors, and the mechanism for breaking and forming links when new crystals freeze. These elements make sense for permutation problems (TSP) but are not directly applicable to continuous spaces, so they were replaced with more universal movement operators that preserve the conceptual essence while being adapted to the geometry of continuous multidimensional space.

Elite Crystal Evolution Algorithm

Figure 1. Flowchart of the ECEA Algorithm

The image above illustrates the concept behind the ECEA algorithm: elite crystals (gold) labeled “E,” ordinary crystals (blue), the center of mass of the elite crystals (red), three movement strategies indicated by arrows, and the Wind Effect. Now we can begin writing the pseudocode for the algorithm.

INITIALIZATION PHASE:

Step 1: Create an array of crystals

Step 2: For each crystal, do the following:

  • For each coordinate in the search space:
    • Generate a random number between zero and one
    • Calculate the position as the minimum value plus a random number multiplied by the range (maximum minus minimum)
  • Set the crystal's status to "non-elite"
  • Reset the stagnation counter

Step 3: Calculate the value of the objective function (fitness) for all crystals

Step 4: Find the global best solution and global worst solution among all crystals

Step 5: Set the iteration counter to zero

Step 6: Set the no-improvement counter to zero

MAIN OPTIMIZATION LOOP:

REPEAT until the termination criterion is met:

PHASE 1: UPDATING ELITE STATUS

Step 7: Increment the iteration counter by one

Step 8: Sort all crystals in descending order of fitness (from best to worst)

Step 9: Assign elite status to the top-ranked crystals after sorting

Step 10: Remove elite status from ordinary crystals

PHASE 2: CALCULATING THE CENTER OF MASS OF ELITE CRYSTALS

Step 11: Set all coordinates of the center of mass to zero

Step 12: Count the number of elite crystals

Step 13: For each elite crystal:

  • For each coordinate:
    • Add the coordinate value of this crystal to the corresponding coordinate of the center of mass

Step 14: If elite crystals have been found:

  • For each coordinate of the center of mass:
    • Divide the accumulated sum by the number of elite crystals

Step 15: If no elite crystals are found (an exceptional situation):

  • Set the center of mass to the global best solution

PHASE 3: CALCULATING RADII OF INFLUENCE

Step 16: For each elite crystal, do the following:

  • Set the minimum distance to another elite crystal to a “very large number”
  • For each other elite crystal:
    • If this is not the same crystal:
      • Calculate the Euclidean distance between them:
        • Reset the sum of squared differences to zero
        • For each coordinate:
          • Calculate the coordinate difference
          • Square it and add it to the sum
        • Take the square root of the sum
      • If this distance is less than the current minimum:
        • Update the minimum distance
  • Set the radius of influence of this crystal to half the minimum distance

PHASE 4: CRYSTAL GROWTH (MAIN MOVEMENT)

Step 17: Calculate the current exploration rate:

  • Calculate the progress as the ratio of the current iteration to 100
  • Calculate the dynamic exploration rate using the following formula

Step 18: For each crystal, do the following:

IF the crystal is elite, THEN:
Step 18a: Local search for an elite crystal:

  • For each coordinate:
    • Calculate the search range as the maximum value minus the minimum value for this coordinate
    • Calculate the step size
    • Generate a random offset
    • Add the offset to the current coordinate of the crystal
ELSE (the crystal is non-elite)

Step 18b: Choose a movement strategy:

  • Generate a random number between zero and one
IF the random number is less than 40 percent, THEN:
Strategy 1: Movement toward the global best solution
  • Calculate the attraction strength using the formula
  • For each coordinate:
    • Calculate the movement direction as the coordinate of the global best solution minus the current coordinate of the crystal
    • Calculate the displacement as the attraction strength multiplied by the direction
    • Add the displacement to the current coordinate
ELSE IF the random number falls within the next 30 percent, THEN:
Strategy 2: Movement toward the elite cluster
  • Find the closest elite crystal to the current one:
    • Set the minimum distance to "a very large number"
    • Set the index of the nearest one to "not found"
    • For each crystal in the population:
      • If this is not the current crystal AND it is elite:
        • Calculate the Euclidean distance to it
        • If this distance is less than the minimum:
          • Update the minimum distance and the index of the nearest one
  • If the nearest elite has been found:
    • For each coordinate:
      • Calculate the direction to the nearest elite as its coordinate minus the current coordinate
      • Calculate the direction toward the center of mass as the center's coordinate minus the current coordinate
      • Generate the first random coefficient between zero and one
      • Generate a second random coefficient between zero and one
      • Calculate the displacement using the formula
      • Add the displacement to the current coordinate
OTHERWISE (the remaining 30 percent):
Strategy 3: Exploratory movement
  • For each coordinate:
    • Calculate the range as the maximum minus the minimum for this coordinate
    • Generate a random number between zero and one
    • IF the random number is less than (seventy percent of thirty percent):
      • Small-scale exploration: calculate the displacement using the formula
    • OTHERWISE:
      • Large-scale exploration: calculate the displacement using the formula
    • Add the displacement to the current coordinate

PHASE 5: WIND EFFECT (DIVERSIFICATION)

Step 19: Generate a random number between zero and one

Step 20: IF the random number is less than the wind probability, THEN:

Apply the Wind Effect:
Step 20a: Find the worst non-elite crystal:

  • Set the worst index to "not found"
  • Set the worst fitness to "a very large positive number"
  • For each crystal in the population:
    • If the crystal is NOT elite AND its fitness is worse than the current worst:
      • Update the worst index and the worst fitness value

Step 20b: If the worst non-elite crystal has been found:

  • Generate a random number between zero and one
  • IF the random number is less than 50 percent:
    • Regeneration strategy near the best solution:
      • For each coordinate:
        • Calculate the range as the maximum minus the minimum
        • Generate noise as a random number using the formula
        • Set the worst crystal's coordinate to the best solution's coordinate plus noise
  • ELSE:
    • Completely random regeneration strategy:
      • For each coordinate:
        • Generate a random number between zero and one
        • Set the coordinate to the minimum plus a random number multiplied by the range

PHASE 6: EVALUATION AND UPDATE

Step 21: Calculate the objective function value (fitness) for all crystals after their displacement

Step 22: Save the current global best fitness value

Step 23: For each crystal, do the following:

  • If the crystal's current fitness is better than its personal best:
    • Update the personal best fitness value
    • Save the current coordinates as the personal best coordinates
  • If the crystal's current fitness is worse than its personal worst:
    • Update the personal worst fitness value
    • Save the current coordinates as the personal worst coordinates
  • If the crystal's current fitness is better than the global best fitness value:
    • Update the global best fitness value
    • Save this crystal's coordinates as the global best coordinates
  • If the crystal's current fitness is worse than the global worst fitness value:
    • Update the global worst fitness value
    • Save this crystal's coordinates as the global worst coordinates

Step 24: Check for improvement:

  • If the absolute difference between the new and saved best fitness values is less than one millionth:
    • Increase the no-improvement counter by one
  • Otherwise:
    • Reset the no-improvement counter

PHASE 7: TERMINATION CRITERION CHECK

Step 25: Check the termination conditions:

  • IF the maximum number of iterations has been reached OR
  • IF the global best solution has reached the target value OR
  • IF the no-improvement counter has exceeded the specified threshold:
    • TERMINATE the loop and proceed to step 26
  • ELSE:
    • RETURN to step 7 (start a new iteration)

ALGORITHM COMPLETION:

Step 26: Return the global best solution found:

  • Coordinates of the best crystal
  • The objective function value at this point
  • Number of iterations completed

END OF ALGORITHM

Let's move on to the implementation. Here is a simplified structure designed to represent the crystal used in a system geared toward continuous optimization. Let's call this structure S_ECEA_Crystal.

Each such crystal has three main characteristics:

  • isElite — a boolean value (true or false) indicating whether a given crystal is elite
  • stagnationCnt — stagnation counter
  • radius — the crystal's radius of influence

The structure also contains an initialization method called Init. When this method is called, the "isElite" value is set to 'false'; that is, by default, the crystal is not considered elite. The stagnation counter stagnationCnt is reset to zero, with the count starting from zero. The radius of influence "radius" is set to zero.

//————————————————————————————————————————————————————————————————————
// Simplified Structure for ECEA (Adaptation to Continuous Optimization)
struct S_ECEA_Crystal
{
    bool   isElite;        // elite crystal
    int    stagnationCnt;  // stagnation counter
    double radius;         // radius of influence

    void Init ()
    {
      isElite       = false;
      stagnationCnt = 0;
      radius        = 0.0;
    }
};
//————————————————————————————————————————————————————————————————————

We will call the class C_AO_ECEA; it will inherit from the base class C_AO and implement our optimization algorithm based on the evolution of elite crystals. The public section of the class sets certain algorithm parameters. We prepare the "params" array to store user-configurable parameters. We set its size to 4 and populate the array.

SetParams() — a method designed to set the algorithm parameter values based on data from the "params" array. After setting the values, it performs validation to ensure that everything remains within the allowed ranges.
Init() —  responsible for the initial initialization of the algorithm. It accepts ranges of minimum and maximum values, steps for the parameters, and the number of epochs.
Moving() — responsible for the main movement or population evolution step within the algorithm.
Revision() — updates or validates the population state and algorithm parameters.

Class data members (externally visible):

  • eliteSize — the number of elite elements.
  • explorationRate — exploration rate.
  • windProbability — the probability of "wind".

Private section of the class (private):

  • crystalData [] — an array containing data about crystals.
  • iterationCount — a value that tracks the current iteration number.
  • noImprovementCount — a value reflecting the number of iterations during which no improvement occurred.
  • centerMass [] — an array that stores the coordinates of the center of mass of the elite elements.

Private helper methods:

  • InitializePopulation() — a method for the initial creation of the population.
  • UpdateEliteStatus() — a method for updating elements' elite status.
  • CrystalGrowth() — a method representing crystal growth, which is analogous to the "deposition" process.
  • WindEffect() — a method that simulates the "wind effect," analogous to "wind-blow" (being blown by the wind).
  • MoveTowardsBest() — this method causes the element with index "idx" to move toward the best solution with a specified strength.
  • MoveTowardsEliteCluster() — this method causes the element with index "idx" to move toward the cluster of elite elements.
  • ExploratoryMove() — This method performs an exploratory movement for the element with index "idx".
  • FindNearestElite() — This method finds the nearest elite element to the element with index "idx".
  • CalculateEliteRadii() — a method for calculating the radii of elite elements.
  • GetDynamicExplorationRate() — a method for retrieving the dynamic (variable) exploration rate.
//————————————————————————————————————————————————————————————————————
class C_AO_ECEA : public C_AO
{
  public:
  ~C_AO_ECEA () { }

  C_AO_ECEA ()
  {
    ao_name = "ECEA";
    ao_desc = "Elite Crystal Evolution Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/20052";

    popSize         = 50;
    eliteSize       = 10;      // elite (analogous to frozen crystals)
    explorationRate = 0.5;     // exploration/exploitation balance
    windProbability = 0.1;     // probability of a "wind" event per iteration

    ArrayResize (params, 4);
    params [0].name = "popSize";           params [0].val = popSize;
    params [1].name = "eliteSize";         params [1].val = eliteSize;
    params [2].name = "explorationRate";   params [2].val = explorationRate;
    params [3].name = "windProbability";   params [3].val = windProbability;
  }

  void SetParams ()
  {
    popSize         = (int)params [0].val;
    eliteSize       = (int)params [1].val;
    explorationRate = params [2].val;
    windProbability = params [3].val;

    if (eliteSize < 2) eliteSize = 2;
    if (eliteSize > popSize / 2) eliteSize = popSize / 2;
    if (explorationRate < 0.1) explorationRate = 0.1;
    if (explorationRate > 0.9) explorationRate = 0.9;
    if (windProbability < 0.0) windProbability = 0.0;
    if (windProbability > 0.5) windProbability = 0.5;
  }

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  int    eliteSize;
  double explorationRate;
  double windProbability;

  private: //---------------------------------------------------------
  S_ECEA_Crystal crystalData [];
  int    iterationCount;
  int    noImprovementCount;
  double centerMass [];                     // center of mass of elite elements

  void   InitializePopulation      ();
  void   UpdateEliteStatus         ();
  void   CrystalGrowth             ();      // analog of deposition
  void   WindEffect                ();      // analog of wind-blow
  void   MoveTowardsBest           (int idx, double strength);
  void   MoveTowardsEliteCluster   (int idx);
  void   ExploratoryMove           (int idx);
  int    FindNearestElite          (int idx);
  void   CalculateEliteRadii       ();
  double GetDynamicExplorationRate ();
};
//————————————————————————————————————————————————————————————————————

The Init function calls the base initialization function StandardInit with the provided parameter ranges and steps. If this base initialization fails, the current function also returns 'false', indicating that it is not possible to continue. For each crystal in the population (from 0 to popSize - 1), its own initialization method is called. If all previous steps were completed successfully, the function returns true, indicating that the initialization was completed correctly.

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

  //------------------------------------------------------------------
  ArrayResize (crystalData, popSize);
  ArrayResize (centerMass,  coords);

  for (int i = 0; i < popSize; i++) crystalData [i].Init ();

  iterationCount     = 0;
  noImprovementCount = 0;

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

The Moving method is called at each optimization iteration. First, the "revision" flag is checked. If "revision" is false, the InitializePopulation() method is called to create the initial population, the "revision" flag is set to true, and the method returns (control proceeds to the next step in the higher-level loop). The iterationCount counter is incremented by one, marking the start of a new iteration.

    Calculating the center of mass of elite elements: The centerMass array, which stores the center-of-mass coordinates, is reset to zero. The algorithm iterates over all population elements (crystalData). If an element is marked as elite, its coordinates are added to the corresponding elements of the centerMass array. The total number of elite elements (eliteCount) is counted.

    If there is at least one elite element (eliteCount > 0): the coordinates in centerMass are divided by the number of elite elements to obtain the average coordinates. If there are no elite elements (eliteCount == 0): the centerMass coordinates are copied from cB. Calculating the radii of influence of elite elements: the CalculateEliteRadii() method is called; it determines how far each elite element's influence extends to other elements in the population.

    The main phase is crystal growth: the CrystalGrowth() method is called, which implements the core part of the algorithm where "crystals" (population elements) "grow" by moving closer to higher-quality solutions.

    Periodic Wind Effect: a random number is generated, and if it is less than windProbability, the WindEffect() method is called, introducing some random "scatter" or "offset" into the positions of the elements and simulating the effect of wind.

      The Moving method controls the population evolution process by checking whether initialization is needed, updating information about the best (elite) elements, simulating growth, and periodically applying random perturbations.

      //————————————————————————————————————————————————————————————————————
      void C_AO_ECEA::Moving ()
      {
        if (!revision)
        {
          InitializePopulation ();
          revision = true;
          return;
        }
      
        //------------------------------------------------------------------
        iterationCount++;
      
        // Calculate the center of mass of the elite elements
        ArrayInitialize (centerMass, 0.0);
        int eliteCount = 0;
        for (int i = 0; i < popSize; i++)
        {
          if (crystalData [i].isElite)
          {
            for (int c = 0; c < coords; c++) centerMass [c] += a [i].c [c];
      
            eliteCount++;
          }
        }
      
        if (eliteCount > 0)
        {
          for (int c = 0; c < coords; c++) centerMass [c] /= (double)eliteCount;
        }
        else
        {
          ArrayCopy (centerMass, cB, 0, 0, coords);
        }
      
        // Calculate the radii of influence of the elite elements
        CalculateEliteRadii ();
      
        // Main phase: crystal growth
        CrystalGrowth ();
      
        // Periodic Wind Effect
        if (u.RNDfromCI (0.0, 1.0) < windProbability)
        {
          WindEffect ();
        }
      }
      //————————————————————————————————————————————————————————————————————

      Initial creation of the population or generation of random initial solutions for the ECEA algorithm. The InitializePopulation function iterates through each element (crystal) in the population, from the first (index 0) to the last (index "popSize - 1"), and generates values for each of its parameters (coordinates), from the first (index 0) to the last (index coords - 1). For each parameter (coordinate "j"), a random value is generated within a specified range. This is done using the u.RNDfromCI random number generator.

      Immediately after a random value is generated, it is "processed" or "quantized" using the u.SeInDiSp function. This ensures that the initial parameter values will satisfy the discrete and step constraints. Thus, InitializePopulation is responsible for initially filling the population with random but valid solutions that will serve as the starting point for further evolution within the algorithm.

      //————————————————————————————————————————————————————————————————————
      void C_AO_ECEA::InitializePopulation ()
      {
        for (int i = 0; i < popSize; i++)
        {
          for (int j = 0; j < coords; j++)
          {
            a [i].c [j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
            a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
          }
        }
      }
      //————————————————————————————————————————————————————————————————————

      The UpdateEliteStatus method is responsible for determining and updating the status of "elite" elements in the population. The method uses "bubble sort" to sort the entire population (both the agents "a" themselves and their associated data crystalData) in descending order of fitness. This means that the elements with the highest fitness will appear at the top of the list. During the sorting process, if the elements being compared have different fitness values, they swap places. At the same time, both the agents themselves (a[j] and a[j+1]) and their data structures (crystalData[j] and crystalData[j+1]) are exchanged to maintain the correspondence between an agent and its characteristics.

      Once the population has been sorted by fitness, the final "elite" status is assigned. For each population element, starting with the best one (index 0) and continuing up to the specified number of best elements (eliteSize), the isElite flag is set to true. All other elements whose indices are greater than or equal to eliteSize are marked as non-elite (their isElite flag is automatically set to 'false' if it was previously true, or simply set to 'false' if it was not).

      Thus, UpdateEliteStatus ensures that after each step in the algorithm, the best solutions (as determined by the fitness function) are explicitly marked as "elite," allowing other parts of the algorithm to use this information for further evolution.

      //————————————————————————————————————————————————————————————————————
      void C_AO_ECEA::UpdateEliteStatus ()
      {
        S_AO_Agent    temp;
        S_CEO_Crystal tempData;
      
        // Sort by fitness
        for (int i = 0; i < popSize - 1; i++)
        {
          for (int j = 0; j < popSize - i - 1; j++)
          {
            if (a [j].f < a [j + 1].f)
            {
              temp = a [j];
              a [j] = a [j + 1];
              a [j + 1] = temp;
      
              tempData = crystalData [j];
              crystalData [j] = crystalData [j + 1];
              crystalData [j + 1] = tempData;
            }
          }
        }
      
        // Update elite status
        for (int i = 0; i < popSize; i++)
        {
          crystalData [i].isElite = (i < eliteSize);
        }
      }
      //————————————————————————————————————————————————————————————————————

      The "crystal growth" process is a key step in the ECEA algorithm. This stage determines how each individual element (crystal) in the population will evolve in order to potentially find better solutions.

      The CrystalGrowth function works as follows: first, the variable "explRate" is calculated; this represents the "dynamic exploration rate." This rate changes as the algorithm runs, making it more or less inclined toward random exploration.

      The method iterates through all elements of the population (popSize). If an element is "elite" (that is, it is among the best solutions identified earlier), it performs local search. For each characteristic (coordinate "c") of an elite element, stepSize is calculated — the size of a possible step, which is proportional to the valid range (rangeMax[c] - rangeMin[c]) and the dynamic exploration rate (explRate). A random number "step" is generated within this "stepSize" (with either a plus or minus sign). This random step is added to the current value of the characteristic (a[i].c[c]).

      The resulting value is again "quantized," or adjusted to the allowed step (rangeStep[c]) within the range (rangeMin[c], rangeMax[c]), similarly to how this was done during initialization. Elite elements "explore" a very limited area around their current position in an attempt to find an even better solution in the immediate vicinity.

      If an element is not "elite": non-elite elements use a combination of different strategies to evolve, with the strategy chosen at random. A random "strategy" number between 0.0 and 1.0 is generated. Depending on the value of "strategy," one of three different strategies is applied:

      • Movement toward the best (with a 40% probability): the MoveTowardsBest function is used, which moves a non-elite element toward the best known solution in the population. The step size depends on the dynamic exploration rate (explRate), making movement toward the best more intensive when exploration is less active.
      • Movement toward the nearest elite element (with a 30% probability): the MoveTowardsEliteCluster function is used. This strategy moves the non-elite element toward the "center" of the group of elite elements or toward its nearest member. This helps non-elite elements move closer to good regions that have already been identified.
      • Exploration (with a 30% probability): the "ExploratoryMove" function is used. This strategy takes broader random steps to explore new, as-yet-unexplored regions of the solution space.

      This method simulates the growth and evolution of a population, where:

      • Elite elements act as "stable, well-developed crystals" that fine-tune their properties within a local region.
      • Non-elite elements use a wider variety of approaches: they may move toward good solutions that have already been found (elite ones), actively seek better locations, or explore new territories in order to become elite in the future.
      //————————————————————————————————————————————————————————————————————
      void C_AO_ECEA::CrystalGrowth ()
      {
        double explRate = GetDynamicExplorationRate ();
      
        for (int i = 0; i < popSize; i++)
        {
          if (crystalData [i].isElite)
          {
            // Elite elements perform local search
            for (int c = 0; c < coords; c++)
            {
              double range = rangeMax [c] - rangeMin [c];
              double stepSize = range * 0.05 * explRate;
              double step = u.RNDfromCI (-stepSize, stepSize);
      
              a [i].c [c] = a [i].c [c] + step;
              a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
            }
          }
          else
          {
            // Non-elite elements: a combination of strategies
            double strategy = u.RNDfromCI (0.0, 1.0);
      
            if (strategy < 0.4)
            {
              // Move toward the best (40%)
              MoveTowardsBest (i, 0.3 + 0.4 * (1.0 - explRate));
            }
            else
              if (strategy < 0.7)
              {
                // Move toward the nearest elite element (30%)
                MoveTowardsEliteCluster (i);
              }
              else
              {
                // Exploration (30%)
                ExploratoryMove (i);
              }
          }
        }
      }
      //————————————————————————————————————————————————————————————————————

      The MoveTowardsBest procedure is designed to move a specific agent (crystal) toward the best solution known so far.

      Parameters:
      • idx is the index of the agent in the population that will be moved.
      • strength — a parameter that determines the "strength" or "amplitude" of the displacement. The greater the "strength," the more strongly the agent will tend toward the best solution.

      Displacement along each coordinate: the method iterates over all the agent's characteristics (coordinates "c") and calculates "direction" for each coordinate "c." This is the difference between the best value of this coordinate, cB [c] (the coordinate of the best agent in the population), and the current value of this coordinate for the agent being moved ("a [idx].c [c]"). The "direction" value indicates which way to move to get closer to the best solution.

      "move" is calculated. This is the step size by which the agent will be displaced along this coordinate. This step is equal to the product of "strength" (the displacement strength) and "direction" (the direction toward the best solution). The agent's current coordinate value is updated by adding the calculated "move" value. After the coordinate value is updated, it is passed through the u.SeInDiSp function. This function adjusts the value so that it remains within the valid range (from rangeMin [c] to rangeMax [c]) and matches the discretization step rangeStep [c].

      The method implements a simple yet effective strategy: if an agent is not elite, it is given the opportunity to "mimic" the best agent in the population. The extent of this "mimicry" is controlled by the "strength" parameter. This helps weaker agents quickly move into promising regions of the solution space where good solutions have already been found.

      //————————————————————————————————————————————————————————————————————
      void C_AO_ECEA::MoveTowardsBest (int idx, double strength)
      {
        for (int c = 0; c < coords; c++)
        {
          double direction = cB [c] - a [idx].c [c];
          double move = strength * direction;
      
          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]);
        }
      }
      //————————————————————————————————————————————————————————————————————


      Conclusion

      We have explored the Elite Crystal Evolution Algorithm (ECEA), which has been adapted for continuous optimization and is based on the crystallization concept from the original Crystal Energy Optimizer (CEO). The proposed approach applies the physical metaphor of a lake freezing to the context of evolutionary search, combining local refinement of solutions by elite crystals with global stochastic search performed by ordinary crystals.

      Thanks to adaptive movement strategies and the Wind Effect mechanism, which ensures population diversification, the algorithm achieves a stable balance between exploration and exploitation of the solution space. As we can see, ECEA shows promise as a versatile method for continuous optimization and can be extended to hybrid systems that combine evolutionary and machine learning approaches.

      In the next article, we will continue describing the specific methods of the ECEA algorithm, test it, and draw conclusions about its performance.

        An archive containing the latest versions of the algorithm code files is attached to the article. The author of this article is not responsible for the absolute accuracy of the descriptions of the canonical algorithms; many of them have been modified 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
        Test function library
        4
        TestStandFunctions.mqh
        Include file
        Test bench function library
        5
        Utilities.mqh
        Include file
        Utility function library
        6
        CalculationTestResults.mqh
        Include file
        Script for calculating results for the comparison table
        7
        Testing AOs.mq5
        Script A unified test bench for all population-based optimization algorithms
        8
        Simple use of population optimization algorithms.mq5
        Script
        A simple example of using population-based optimization algorithms without visualization
        9
        Test_AO_ECEA.mq5
        Script Test bench for ECEA

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

        Attached files |
        ECEA.zip (297.47 KB)
        Features of Custom Indicators Creation Features of Custom Indicators Creation
        Creation of Custom Indicators in the MetaTrader trading system has a number of features.
        Unified Multi-Timeframe Renko: Synthesizing the Market's Temporal Dimensions Unified Multi-Timeframe Renko: Synthesizing the Market's Temporal Dimensions
        The article presents an innovative concept for a multi-timeframe Renko chart that combines signals from four timeframes (M5, M15, H1, H4) into a unified synthetic instrument. The system creates a virtual symbol in MetaTrader 5 by using the EMA of each timeframe to generate a composite signal through three methods: simple average, weighted average, and consensus. The implementation includes ATR-based adaptive brick sizing, real-time operation, and full integration with MetaTrader 5.
        Features of Experts Advisors Features of Experts Advisors
        Creation of expert advisors in the MetaTrader trading system has a number of features.
        Designing a Unified Order Execution Gateway Class in MQL5 Designing a Unified Order Execution Gateway Class in MQL5
        This class provides one point of contact for trade operations in MQL5. It rounds and clamps lot sizes, validates SL/TP against the broker's minimum distance, resolves a compatible filling policy, and applies bounded retries for transient retcodes. Calls return a structured CGatewayResult instead of raw retcodes, simplifying error handling and maintenance across strategies.