Русский Español Português
preview
Exchange Market Algorithm (EMA)

Exchange Market Algorithm (EMA)

MetaTrader 5Trading systems |
369 0
Andrey Dik
Andrey Dik


Contents

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


Introduction

In the field of numerical optimization, new algorithms are continuously being developed that aim to effectively solve complex problems under conditions of uncertainty and multidimensionality. Among these, population-based metaheuristics occupy a special place. By simulating natural or social processes, they demonstrate impressive abilities in finding global optima.

However, not every new algorithm is capable of achieving competitive performance levels. This article presents a detailed analysis of the Exchange Market Algorithm (EMA), a representative of the aforementioned class of methods inspired by the behavior of stock market traders. The algorithm simulates stock trading, where market participants with varying degrees of success employ various strategies to maximize profits. 


Implementation of the algorithm

The algorithm begins by creating a population of market participants (traders), which is then divided into three equal groups. Each trader is randomly assigned a starting position in the search space representing their current stock portfolio. These positions are evenly distributed across the entire acceptable range of values.

After an initial assessment of the success of each trader (calculation of the objective function value), the entire population is sorted in descending order of success and divided into three groups:

  • The first group is the elite. These are the most successful market participants who have found the most profitable positions. An important feature: elite traders maintain their positions unchanged throughout the entire trading session, serving as an example for others. 
  • The second group is the middle class. Moderately successful participants who seek to improve their position by learning from the elite, but are also willing to take moderate risks. 
  • The third group consists of beginners. The least successful participants, willing to take high risks in an attempt to radically improve their situation.

Each iteration of the algorithm represents a trading session consisting of two phases corresponding to different market states.

Phase 1: Balanced market (stable trading) 


In stable market conditions, traders use conservative strategies based on imitating more successful participants. 

Middle class behavior. Each trader in the second group selects a random mentor from the elite and gradually shifts their position in that direction. The extent of this shift is determined by the absorption coefficient (r1 = 1.5), which adaptively decreases as the algorithm progresses. The movement is not deterministic; a random multiplier is used to simulate the uncertainty of market decisions. 

Beginners' behavior. Traders in the third group are influenced by both the elite and the middle class. Each beginner chooses one mentor from the first and second groups, and their new position is formed as a combination of movements in the direction of both mentors. This reflects an attempt to learn simultaneously from the most successful traders and from those who have recently improved their positions.

Phase 2: The fluctuating market (volatile trading)


In volatile market conditions, traders use more aggressive strategies to explore new opportunities. 

Exploratory behavior of the middle class. Traders in the second group use a dual strategy. There is a 50% chance that they can completely copy the position of the globally best solution (the most successful trader in history). In other cases, they explore the area around the elite group's center of mass, adding controlled random noise to that position. The noise level is determined by the risk factor (riskAlpha = 0.3) and decreases over time.

Aggressive behavior of beginners. The third group uses the most risky strategies:

  • with a probability equal to the risk factor (30%), the trader completely forgets his current position and starts again from a random point;
  • in 35% of cases, a wide search is performed around the current position with a large radius;
  • in the remaining cases, oppositional learning is used: the trader moves in the direction opposite to the center of the worst decisions, trying to get away from unsuccessful areas.

The key feature of EMA is its adaptability. As the iterations progress, the decay rate is calculated based on the sigmoid function. This coefficient ensures a smooth transition from exploration (at the beginning of the work) to the exploitation of promising solutions already found (at the end).

After both phases are completed, an important step occurs: the positions of all traders except the elite are updated according to the calculated new values. Elite traders retain their positions, which ensures the preservation of the best solutions found.

Then, a new success rate (the value of the objective function) is calculated for all traders, the population is sorted again, and a new globally best solution is determined. Now let's see what the algorithm formulas look like in the image below.

ema_illustracion

The image contains all the key position update formulas for each group in both market modes. Based on the description and formulas above, we can now formulate the EMA pseudocode.

1. Initialization

  • Create a population of N = 60 traders (N is a multiple of 3)
  • Set parameters: r₁ = 1.5, r₂ = 0.8, α = 0.3 (risk factor)
  • Initialize each trader with a random position
  • Determine the group size: m = N/3 = 20

2. Main loop

For each iteration t = 1, 2, ..., T, execute:

2.1 Ranking

  • Assess the fitness of all traders
  • Sort by fitness level descending
  • Divide into groups:
    • G₁ = {1, ..., m} — elite
    • G₂ = {m+1, ..., 2m} — middle class
    • G₃ = {2m+1, ..., N} — beginners

2.2 Adaptation of parameters

  • progress = t / T
  • decay = 1 / (1 + exp(-10(progress - 0.5)))
  • r₁ᵃᵈᵃᵖᵗ = r₁ × (1 - decay × 0.5)
  • r₂ᵃᵈᵃᵖᵗ = r₂ × (1 - decay × 0.3)

2.3 Balanced market

  • G₁: positions are saved
  • G₂: for each i ∈ G₂
    • select a random leader k ∈ G₁
    • xᵢ = xᵢ + rand() × r₁ᵃᵈᵃᵖᵗ × (xₖ - xᵢ)
  • G₃: for each i ∈ G₃
    • select a leader k₁ ∈ G₁ and k₂ ∈ G₂
    • xᵢ = xᵢ + rand() × r₁ᵃᵈᵃᵖᵗ × (xₖ₁ - xᵢ) + rand() × r₂ᵃᵈᵃᵖᵗ × (xₖ₂ - xᵢ)

2.4 Fluctuating market

  • G₂: for each i ∈ G₂
    • if rand() < 0.5: xᵢ = xᵇᵉˢᵗ
    • otherwise: xᵢ = center(G₁) + noise × α × (1 - decay × 0.5)
  • G₃: for each i ∈ G₃
    • if rand() < α: xᵢ = random_position()
    • otherwise if rand() < 0.5 + α/2: xᵢ = xᵢ + big_disturbance
    • otherwise: xᵢ = 2xᵢ - center(worst) + small_noise

2.5 Update

  • Apply new positions for G₂ and G₃
  • Update globally the best solution

3. Returning the result

Return the best solution found xᵇᵉˢᵗ

Now we can write the C_AO_EMA class, which will be a descendant of the C_AO class and implement our EMA optimization algorithm. Main components: popSize is the population size (number of agents), r1, r2 are absorption coefficients for different groups of agents, riskAlpha is the risk factor. Next, the parameters for setting up the algorithm are initialized and default values are assigned.

Functions:

  • SetParams () — update the values of the algorithm parameters from the params array, and also performs a validation check on these parameters to ensure they are within acceptable ranges.
  • Init () — initialize the algorithm before using it, accept parameters such as search bounds (rangeMinP, rangeMaxP), parameter step (rangeStepP) and number of epochs (epochsP).
  • Moving () — move agents in the search space and represent the main logic of the EMA algorithm for updating the agent positions.
  • Revision() — revise agent strategies, including mechanisms for improving and changing the current positions of agents.
  • GetDecayRate() — return the decay rate.

Variables:

  • r1 — absorption coefficient for a group of agents. A higher value means that agents in this group are more strongly "attracted" to other agents or to "better" solutions.
  • r2 — absorption coefficient for another group of agents.
  • riskAlpha — risk factor that determines how much risk agents are exposed to when making decisions.
  • groupSize — size of each of the three groups into which the population is divided (popSize / 3).
  • currentEpoch — current algorithm iteration index.
  • totalEpochs — total number of iterations to be performed by the algorithm.
  • tempPop — a temporary array for storing new positions of agents as they move. The S_AO_Agent type defines the structure of a single agent (decision).

The C_AO_EMA class provides an implementation of the EMA algorithm for numerical optimization. The algorithm divides the population into groups with different strategies (defined by the coefficients r1 and r2) and uses a risk factor (riskAlpha) to balance between exploring the search space and using the "good" solutions found. The Moving() and Revision() functions contain the main optimization logic, Init() initializes the algorithm, and SetParams() allows us to change the parameter values.

//————————————————————————————————————————————————————————————————————
class C_AO_EMA : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_EMA () { }
  C_AO_EMA ()
  {
    ao_name = "EMA";
    ao_desc = "Exchange Market Algorithm";
    ao_link = "https://www.mql5.com/en/articles/18605";

    popSize   = 60;     // Population size
    r1        = 1.5;    // Absorption coefficient for group 2
    r2        = 0.8;    // Absorption coefficient for group 3
    riskAlpha = 0.3;    // Risk factor

    ArrayResize (params, 4);

    params [0].name = "popSize";   params [0].val = popSize;
    params [1].name = "r1";        params [1].val = r1;
    params [2].name = "r2";        params [2].val = r2;
    params [3].name = "riskAlpha"; params [3].val = riskAlpha;
  }

  void SetParams ()
  {
    popSize   = (int)params [0].val;
    r1        = params      [1].val;
    r2        = params      [2].val;
    riskAlpha = params      [3].val;

    // Check the parameters validity
    if (popSize < 6) popSize = 6;
    if (popSize % 3 != 0) popSize = ((popSize / 3) + 1) * 3; // Multiple of 3
    if (r1 < 0.0) r1 = 0.0;
    if (r2 < 0.0) r2 = 0.0;
    if (riskAlpha < 0.0) riskAlpha = 0.0;
    if (riskAlpha > 1.0) riskAlpha = 1.0;
  }

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  double r1;         // Absorption coefficient 1
  double r2;         // Absorption coefficient 2
  double riskAlpha;  // Risk factor

  private: //---------------------------------------------------------
  int    groupSize;     // size of each group (popSize/3)
  int    currentEpoch;  // current epoch
  int    totalEpochs;   // total number of epochs

  S_AO_Agent tempPop []; // temporary population for storing new positions

  double GetDecayRate   ();
};
//————————————————————————————————————————————————————————————————————

The Init initialization method is intended to prepare the algorithm for execution. It takes arrays of minimum values, maximum values, and steps for the search parameters, as well as the number of epochs (iterations). First, the standard initialization function is called, which handles setting up search ranges and checking their validity. If it returns 'false', then the initialization of the method is terminated.

Next, the size of each agent group is calculated by dividing the total population size by 3. The current epoch counter is initialized and the total number of epochs the algorithm is designed for is stored. After this, memory is allocated for a temporary population - an array of agents that has the same size as the main population. In the loop, each agent in this temporary array is initialized with coordinates specified in the "coords" array.

When all these steps are completed successfully, the return value is 'true', which means that the algorithm is successfully prepared for execution.

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

  //------------------------------------------------------------------
  groupSize    = popSize / 3;
  currentEpoch = 0;
  totalEpochs  = epochsP;

  // Initialize the temporary population
  ArrayResize (tempPop, popSize);
  for (int i = 0; i < popSize; i++)
  {
    tempPop [i].Init (coords);
  }

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

The Moving method is the main function that implements the evolution of agents in the EMA algorithm. It is responsible for changing the positions of agents in the search space. Initial setup (performed only once):

  • If this is the first run of the algorithm, then random initialization of the coordinates of all agents occurs within the specified ranges (rangeMin, rangeMax).
  • The coordinates of each agent are adjusted taking into account the discretization step (rangeStep).
  • The revision variable is set to 'true' so that subsequent calls to this method do not repeat the initialization.

Epoch and decay rate update:

  • The current epoch counter (currentEpoch) is incremented.
  • The decayRate is calculated which decreases with each epoch, influencing the adaptive behavior of the algorithm.

Copying the current population. The coordinates and values of the f objective function of all agents from the current 'a' population are copied to the tempPop temporary population, which is done so that changes in tempPop do not affect 'a' until all calculations in the current iteration are completed.

PHASE 1: Balanced market (absorbing operators). This phase models the behavior of agents that "absorb" information from the best agents. The population is divided into three groups:

  • Group 1 (elite): These agents (with indices from 0 to groupSize - 1) do not change at this stage, they are the best agents, and their positions are maintained.
  • Group 2 (absorbing operator 1): agents of this group (with indices from groupSize to 2*groupSize - 1) update their positions.
    • For each agent in this group, a "leader" is randomly selected from Group 1.
    • The coordinates of the agent from Group 2 are shifted towards the coordinates of the selected leader from Group 1. The offset depends on the r1 adaptive coefficient, which decreases taking decayRate into account.
    • Coordinates are adjusted by range and step.
  • Group 3 (absorbing operator 2): agents of this group (with indices from "2 * groupSize" to "popSize - 1") also update their positions, but more actively.
    • For each agent in this group, two leaders are randomly selected: one from Group 1 and one from Group 2.
    • The coordinates of the agent from Group 3 are shifted towards both selected leaders using the same r1 adaptive coefficient.
    • Coordinates are adjusted by range and step.

PHASE 2: Fluctuating market (search operators). This phase simulates the exploratory behavior of agents, allowing them to search for new areas in space.

  • Group 2 (search operator 1 - moderate risk):
    • Each agent coordinate from this group is updated.
    • There is a 50% chance that the coordinate is completely replaced by the value from cB.
    • Otherwise, the coordinate is shifted relative to the "elite center" (the average value of the coordinates of Group 1 agents) with the addition of noise. The noise depends on riskAlpha, coordinate range and decayRate.
    • Coordinates are adjusted by range and step.
  • Group 3 (search operator 2 - high risk):
    • Each agent coordinate from this group is updated.
    • With the riskAlpha probability the coordinate is completely reinitialized randomly within the range. This simulates a very risky, large-scale study.
    • Otherwise, if the second random number is less than "0.5 + riskAlpha / 2.0", a "wide search" occurs: the coordinate is shifted by a random amount within a certain radius, depending on riskAlpha and decayRate.
    • Otherwise, "opposition-based training" is applied: the coordinate is shifted in the opposite direction. A little random noise is added.
    • Coordinates are adjusted by range and step.

Copying changes. Finally, after all calculations, the updated coordinates of the agents from tempPop (for Groups 2 and 3) are copied back to the 'a' main population. The coordinates of Group 1 remain unchanged (they were not copied back initially).

In general, the Moving method implements an iterative process where agents in the population change their positions by "learning" from the best agents and exploring the search space with varying degrees of risk. 

//————————————————————————————————————————————————————————————————————
//--- Main loop of the algorithm
void C_AO_EMA::Moving ()
{
  // Initial setup
  if (!revision)
  {
    for (int i = 0; i < popSize; i++)
    {
      for (int c = 0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
        a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }

    revision = true;
    return;
  }

  currentEpoch++;
  double decayRate = GetDecayRate ();

  // Copy the current population to the temporary one
  for (int i = 0; i < popSize; i++)
  {
    ArrayCopy (tempPop [i].c, a [i].c, 0, 0, WHOLE_ARRAY);
    tempPop [i].f = a [i].f;
  }

  // PHASE 1: Balanced market (absorbing operators)

  // Group 1 (elite) - does not change
  // Indices: 0 ... groupSize-1

  // Group 2 - absorbing operator 1
  // Indices: groupSize ... 2*groupSize-1

  double adaptiveR1 = r1 * (1.0 - decayRate * 0.5);

  for (int i = groupSize; i < 2 * groupSize; i++)
  {
    // Each agent in group 2 selects a random leader from group 1
    int leaderIdx = u.RNDminusOne (groupSize);

    for (int c = 0; c < coords; c++)
    {
      tempPop [i].c [c] = a [i].c [c] + u.RNDprobab () * adaptiveR1 * (a [leaderIdx].c [c] - a [i].c [c]);
      tempPop [i].c [c] = u.SeInDiSp (tempPop [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }

  // Group 3 - absorbing operator 2
  // Indices: 2*groupSize ... popSize-1

  adaptiveR1 = r1 * (1.0 - decayRate * 0.3);

  for (int i = 2 * groupSize; i < popSize; i++)
  {
    // Selection of leaders from groups 1 and 2
    int leader1Idx = u.RNDminusOne (groupSize);
    int leader2Idx = groupSize + u.RNDminusOne (groupSize);

    for (int c = 0; c < coords; c++)
    {
      tempPop [i].c [c] = a [i].c [c] +
                          u.RNDprobab () * adaptiveR1 * (a [leader1Idx].c [c] - a [i].c [c]) +
                          u.RNDprobab () * adaptiveR1 * (a [leader2Idx].c [c] - a [i].c [c]);

      tempPop [i].c [c] = u.SeInDiSp (tempPop [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }

  // PHASE 2: Fluctuating market (search operators)

  // Group 2 - search operator 1 (moderate risk)
  for (int i = groupSize; i < 2 * groupSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      double range = rangeMax [c] - rangeMin [c];

      if (u.RNDprobab () < 0.5)
      {
        tempPop [i].c [c] = cB [c];// tempPop [i].c [c] + delta;
      }
      else
      {
        // Search around the center of the elite
        double eliteCenter = 0.0;
        for (int j = 0; j < groupSize; j++)
        {
          eliteCenter += a [j].c [c];
        }
        eliteCenter /= (double)groupSize;

        double noise = riskAlpha * range * u.RNDfromCI (-0.5, 0.5) * (1.0 - decayRate * 0.5);
        tempPop [i].c [c] = eliteCenter + noise;
      }

      // Check boundaries
      tempPop [i].c [c] = u.SeInDiSp (tempPop [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }

  // Group 3 - search operator 2 (high risk)
  for (int i = 2 * groupSize; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      double range = rangeMax [c] - rangeMin [c];

      if (u.RNDprobab () < riskAlpha)
      {
        // Full reinitialization
        tempPop [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
      }
      else
        if (u.RNDprobab () < 0.5 + riskAlpha / 2.0)
        {
          // Broad search
          double searchRadius = 2.0 * riskAlpha * range * (1.0 - decayRate * 0.3);
          double delta = u.RNDfromCI (-searchRadius, searchRadius);

          tempPop [i].c [c] = tempPop [i].c [c] + delta;
        }
        else
        {
          // Opposition-based training
          double worstCenter = 0.0;
          int worstCount = groupSize / 2;
          for (int j = popSize - worstCount; j < popSize; j++)
          {
            worstCenter += a [j].c [c];
          }
          worstCenter /= (double)worstCount;

          // Moving in the opposite direction from the worst ones
          tempPop [i].c [c] = 2.0 * tempPop [i].c [c] - worstCenter;

          // Add a little noise
          double noise = riskAlpha * range * u.RNDfromCI (-0.1, 0.1);
          tempPop [i].c [c] += noise;
        }

      // Check boundaries
      tempPop [i].c [c] = u.SeInDiSp (tempPop [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }

  // Copying from the temporary population to the main one (except group 1)
  for (int i = groupSize; i < popSize; i++)
  {
    ArrayCopy (a [i].c, tempPop [i].c, 0, 0, WHOLE_ARRAY);
  }
}
//————————————————————————————————————————————————————————————————————

The GetDecayRate method is used to calculate the decay rate, which controls the degree to which the algorithm behavior changes during execution. It first checks if the total number of totalEpochs is "0" or negative, then returns zero to avoid division by zero and ensure safe operation. Next, it calculates the progress of the search as a proportion of past epochs relative to the total number of "progress". The "progress" value increases from "0" to "1" as the algorithm progresses.

Using "progress", the method returns a value based on the sigmoid function of exponential form. This function provides a smooth transition of the attenuation factor from "0" to "1", making it so that at the beginning of the search (when "progress" is close to 0) the value is close to "0", and at the end (when "progress" approaches 1) - to 1. This non-linear transition helps to offset the balance between exploring space and exploiting already found solutions.

Specifically, a sigmoid function of the form: 1 / (1 + exp(-10 * (progress - 0.5))) is used. This form yields a steeper transition near the midpoint of the optimization process "progress ≈ 0.5", which contributes to a more dynamic change in the coefficient during the algorithm operation.

//————————————————————————————————————————————————————————————————————
//--- Get the decay rate
double C_AO_EMA::GetDecayRate ()
{
  if (totalEpochs <= 0) return 0.0;

  // Non-linear attenuation for better exploitation/exploration balance
  double progress = (double)currentEpoch / (double)totalEpochs;

  // Use the sigmoid function for a smooth transition
  return 1.0 / (1.0 + MathExp (-10.0 * (progress - 0.5)));
}
//————————————————————————————————————————————————————————————————————

The Revision method in the context of the EMA algorithm is responsible for updating the best solutions found (global optimum) at each iteration. A temporary aT static array of S_AO_Agent type is created. "static" means that this array will be created once the function is first called and will retain its state between calls. "ArrayResize (aT, popSize)";  resizes this temporary array so that it can accommodate popSize (the population size) of agents. This array will be used for sorting. The Sorting function is called from the auxiliary 'u' object. The function sorts the main population of 'a' agents by their fitness (the value of the 'f' objective function). 

Important: After this operation, the 'a' array will be sorted in such a way that the agent with the best value of the 'f' objective function will be in first place (index "0"). In minimization algorithms, this will be the agent with the smallest 'f', in maximization algorithms, the one with the largest 'f'. The coordinates of the current population's best agent (now located at a[0] after sorting) are copied into the cB array, which stores the coordinates of the best solution found by the algorithm so far. The best agent's objective function value is assigned to the fB variable. This variable stores the value of the objective function corresponding to the best cB coordinates.

//————————————————————————————————————————————————————————————————————
//--- Update the best solutions
void C_AO_EMA::Revision ()
{
  static S_AO_Agent aT []; ArrayResize (aT, popSize);
  u.Sorting (a, aT, popSize);
  ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY);
  fB = a [0].f;
}
//————————————————————————————————————————————————————————————————————


Test results

The test results are far from perfect, but the algorithm works and has certain search capabilities.

EMA|Exchange Market Algorithm|60.0|1.5|0.8|0.3|
=============================
5 Hilly's; Func runs: 10000; result: 0.6706604188712635
25 Hilly's; Func runs: 10000; result: 0.42759923501764946
500 Hilly's; Func runs: 10000; result: 0.252217676777693
=============================
5 Forest's; Func runs: 10000; result: 0.7419215403847332
25 Forest's; Func runs: 10000; result: 0.38137087707323236
500 Forest's; Func runs: 10000; result: 0.19454127467011006
=============================
5 Megacity's; Func runs: 10000; result: 0.38769230769230767
25 Megacity's; Func runs: 10000; result: 0.21323076923076928
500 Megacity's; Func runs: 10000; result: 0.09672307692307769
=============================
All score: 3.36596 (37.40%)

The visualization shows a scatter in the results for low-dimensional functions.

Hilly

EMA on the Hilly test function

Forest

EMA on the Forest test function

Megacity

EMA on the Megacity test function

After testing, the EMA algorithm will be included in our ranking table of population optimization algorithms for reference only.

# 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 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
21 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
22 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
23 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
24 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
25 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
26 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
27 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
28 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
29 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
30 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
31 (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
32 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
33 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
34 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
35 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
36 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
37 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
38 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
39 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
40 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
41 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
42 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
43 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
44 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
45 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
EMA exchange_market_algorithm 0.67066 0.42759 0.25221 1.35046 0.74192 0.38137 0.19454 1.31783 0.38769 0.21323 0.09672 0.69764 3.366 37.40
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


Summary

The presented EMA algorithm, although possessing basic search capabilities, does not demonstrate sufficient performance to enter the top 45 of the population optimization algorithms ranking table. Its monolithic structure and the lack of explicit mechanisms to overcome the well-known problems associated with getting stuck in local extrema point to a number of potential shortcomings. 

Without significant enhancements to its functionality and adaptive behavior control, EMA will remain a basic algorithm with limited potential for solving complex optimization problems, which explains its absence from top metaheuristics rankings. However, the ideas presented in the algorithm are quite interesting and promising for our pool of ideas for further development.

tab

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

chart

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

EMA pros and cons:

Pros:

  1. simple implementation

Cons:

  1. low convergence accuracy

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_EMA.mq5
Script EMA test stand

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

Attached files |
EMA.zip (242.9 KB)
Carry Trade Logic in MQL5: Building an EA That Factors Swap Rates Into Position Sizing and Holding Decisions Carry Trade Logic in MQL5: Building an EA That Factors Swap Rates Into Position Sizing and Holding Decisions
Most retail traders ignore overnight swap rates, but for long-term positions, these interest payments can make or break your strategy. This article shows you how to build a dynamic MQL5 module that retrieves real-time swap data and converts it into actual profit or loss in your account currency. You will learn how to program an Expert Advisor that automatically calculates if a trade is worth holding based on carry income and adjusts your position size to account for expected interest. It is a practical guide to turning a hidden cost into a mathematical advantage for your trading systems.
Developing a Multi-Currency Expert Advisor (Part 28): Adding a Position Closing Manager Developing a Multi-Currency Expert Advisor (Part 28): Adding a Position Closing Manager
When running multiple strategies in parallel, you may want to periodically close all open positions and start the strategies over again. The existing code only allows this behavior to be implemented through manual intervention. Let's try to automate this part.
Beyond GARCH (Part V): Fitting the Multifractal Spectrum in MQL5 Beyond GARCH (Part V): Fitting the Multifractal Spectrum in MQL5
This article builds the Spectrum Fitter: from tau(q) we compute f(alpha) with a discrete Legendre transform, then fit Normal, Binomial, Poisson, and Gamma spectra under box constraints using BLEIC. The best model by SSE is selected, and its parameters (eg, alpha min, alpha max or alpha_0, gamma) become the cascade inputs for multifractal simulation.
MQL5 Wizard Techniques you should know (Part 93): Using Suffix Automation and an Auto Encoder in a Custom Money Management Class MQL5 Wizard Techniques you should know (Part 93): Using Suffix Automation and an Auto Encoder in a Custom Money Management Class
For this article we switch to a custom MQL5 Wizard class implementation that explores Money Management. We are labelling our custom class ‘CMoneySuffixAE’ that we derive by combining the Suffix Automaton algorithm with an Autoencoder neural network. As always, this formulation is testable with MQL5 Wizard Assembled Expert Advisors that can be tuned with various entry signals and trailing stop approaches.