Русский Português
preview
Dingo Optimization Algorithm Modification (DOAm)

Dingo Optimization Algorithm Modification (DOAm)

MetaTrader 5Trading systems |
4 120 2
Andrey Dik
Andrey Dik

Contents

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


Introduction

Let's ask ourselves: are there population-based metaheuristic algorithms that, with a limited number of iterations — 10,000, which is our case — are capable of delivering a 100% results? Its power should be pretty overwhelming. In fact, sometimes I doubt the feasibility of discovering such a gem in the optimization trade.  

Similar algorithms for such a purpose may exist, and today we will get acquainted with one of the unique optimization methods, or rather, a modified version of the algorithm I have already described in the previous article of the Dingo Optimization Algorithm, which brings us closer to solving such a difficult task.


Implementation of the algorithm

While working on the Dingo Optimization Algorithm, I became interested in the principle of modeling dingo hunting, which has several strategies. We got acquainted with this algorithm and analyzed in detail the principles of its operation. The algorithm's test yielded average results; it did not make it into the ranking table, as it was still 20% short of the entry threshold. It would seem that we can put it on the shelf and move on. However, I decided to try experimenting with the formulas, and this is what I ended up with:

Group attack formula (Equation 2), original version:

x⃗ᵢ(t+1) = β₁ · [Σₖ₌₁ⁿᵃ φ⃗ₖ(t)]/nₐ - x⃗*(t)

now looks like this:

a[agentIdx].c[c] = cB[c] + beta1 * sumAttack;

As you can see, the sign has been changed - instead of subtracting the best solution, we use addition. Also, the method for calculating sumAttack is more complicated - it is divided by (na * coords), instead of just "na".

The survival threshold in the original version is survival[r] <= 0.3, let's change it to survival[r] <= 0.01. We are sharply lowering the threshold, which means that we will only apply survival strategies to the "weakest" agents.

The original scavenging formula (Equation 4):

x⃗ᵢ(t+1) = ½ · e^β₂ · |x⃗ᵣ₁(t) - (-1)^σ · x⃗ᵢ(t)|

New version:

a[agentIdx].c[c] = (MathExp(beta2) * a[r1].c[c] - sign * a[agentIdx].c[c]) / 2.0;

Now the module (absolute value) is removed allowing for negative coordinates. In addition, the modified algorithm averages the positional difference across all coordinates simultaneously rather than coordinate by coordinate.

Why might these changes improve the algorithm's performance? Change of sign in group attack can create a movement "towards a better solution, taking into account the positions of others," rather than movement away from the best solution, as in the authors’ version. Lowering the survival threshold allows most agents to use basic hunting strategies, using survival only in extreme cases. The absence of a module in the scavenging formula allows for freer movement in the search space.

DOA_M

Figure 1. DOAm_dingo strategy modifications

The illustration above shows the key differences between the modified version and the original one described by the algorithm authors: in a group attack, the movement is carried out towards a leader instead of moving from it, the survival threshold is lowered down to 0.01, now scavenging is done without a module in the formula and averaging is performed by (na × coords) instead of just (na). Modifications are highlighted in red for clarity.

Now let's move on to describing the algorithm code and see what we have got. The C_AO_DOAm_dingo class is a specialized version of the C_AO base class. It is an optimization algorithm based on modifying the behavior of dingoes, so the Dingo Optimization Algorithm M class inherits from C_AO, which implies that it has the basic functionality. The following default values are set for the key parameters:

  • popSize (population size, i.e. number of dingoes): 50.
  • P (probability of choosing between hunting and scavenging): 0.5.
  • Q (probability of choosing between group attack and chase): 0.7.
The "params" array is prepared to store parameters and their values, setting initial values according to popSize, P, and Q. The SetParams method allows us to update the parameter values (popSize, P, Q) from an external source (from the "params" array). Methods: Init, Moving, Revision are defined in the C_AO base class and are overridden here to implement the specific logic of the algorithm.
  • Init — initial launch of the algorithm, including the initialization of agents (dingoes) and their parameters, using the provided ranges and change steps.
  • Moving — the main phase of the algorithm, where agents move and interact in accordance with the rules of the algorithm.
  • Revision — revising or evaluating the state of the algorithm after a certain stage or epoch.
Public member variables:
  • P — probability that a dingo will hunt or scavenge.
  • Q — probability that a dingo will participate in a group attack or chase.
Private member variables:
  • beta1, beta2 - used to generate random vectors or steps in the optimization with given ranges of values;
  • naIni, naEnd — define the minimum and maximum number of dingoes participating in the attack, which can vary;
  • survival — array that stores the "survival" or fitness indicators of each dingo.
Private auxiliary methods: these methods implement the internal logic of the dingo algorithm:
  • UpdateSurvivalRates — update the survival rates of agents;
  • GroupAttack — implement group attack behavior;
  • Persecution — implement chase behavior;
  • Scavenger — implement the scavenging behavior;
  • SurvivalProcedure — procedure related to the survival of agents;
  • GetAttackVector — get a vector that determines the attack direction;
  • CalculateAttackSum — calculate the total attack "effect".
//————————————————————————————————————————————————————————————————————
class C_AO_DOAm_dingo : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_DOAm_dingo () { } 
  C_AO_DOAm_dingo ()
  {
    ao_name = "DOAm";
    ao_desc = "Dingo Optimization Algorithm M";
    ao_link = "https://www.mql5.com/en/articles/19187";

    popSize = 50;     // population size (number of dingoes)
    P       = 0.5;    // Hunting or Scavenger rate
    Q       = 0.7;    // Group attack or chase

    ArrayResize (params, 3);

    params [0].name = "popSize"; params [0].val = popSize;
    params [1].name = "P";       params [1].val = P;
    params [2].name = "Q";       params [2].val = Q;
  }

  void SetParams ()
  {
    popSize = (int)params [0].val;
    P       = params [1].val;
    Q       = params [2].val;
  }

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  double P;          // hunting or scavenging probability
  double Q;          // group attack or chase probability

  private: //---------------------------------------------------------
  double beta1;       // -2 < beta1 < 2
  double beta2;       // -1 < beta2 < 1
  int    naIni;       // minimum number of attacking dingoes
  int    naEnd;       // maximum number of attacking dingoes
  double survival []; // array of survival indicators

  // Auxiliary methods
  void   UpdateSurvivalRates ();
  void   GroupAttack         (int agentIdx, int na);
  void   Persecution         (int agentIdx);
  void   Scavenger           (int agentIdx);
  void   SurvivalProcedure   (int agentIdx);
  void   GetAttackVector     (int na, int &attackVector []);
  double CalculateAttackSum  (int agentIdx, int na);
};
//————————————————————————————————————————————————————————————————————

This method is designed to initialize the Dingo Optimization Algorithm M algorithm. It prepares the algorithm for operation by setting the initial conditions for optimization, determining how dingoes will form attack groups, and setting the initial state of their ‘survival’ state.

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

  //------------------------------------------------------------------
  // Initialize algorithm parameters
  naIni = 2;               // minimum number of attackers
  naEnd = popSize / naIni; // maximum number of attackers

  ArrayResize     (survival, popSize);
  ArrayInitialize (survival, 1.0);

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

The Moving method is the main function responsible for executing one step (or iteration) of the algorithm, it simulates the behavior of a pack of dingoes while they search for the optimal solution. The method dynamically controls the behavior of the dingo population, allowing them to explore the solution space using different strategies based on their "survival" and probabilistic decisions. A more detailed description of the main methods was given in the previous article. Here we will look more closely at the methods specific to this implementation.

//————————————————————————————————————————————————————————————————————
//--- Main step of the algorithm
void C_AO_DOAm_dingo::Moving ()
{
  // Starting initialization of the population
  if (!revision)
  {
    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]);
      }
    }

    revision = true;
    return;
  }

  //------------------------------------------------------------------
  // Update survival rates
  UpdateSurvivalRates ();

  // Update beta parameters for the current iteration
  //beta1 = -2.0 + 4.0 * u.RNDprobab ();
  //beta2 = -1.0 + 2.0 * u.RNDprobab ();
  beta1 = u.RNDfromCI (-2.0, 2.0);
  beta2 = u.RNDfromCI (-1.0, 1.0);

  // Main loop for all dingoes
  for (int r = 0; r < popSize; r++)
  {
    // Check the survival rate (Section 2.2.4)
    //if (survival[r] <= 0.3)
    if (survival [r] <= 0.01)
    {
      // Strategy 4: Survival (Eq. 6)
      SurvivalProcedure (r);
    }
    else
    {
      // Choose between hunting and scavenging
      if (u.RNDprobab () < P)  // If hunting
      {
        if (u.RNDprobab () < Q)  // If group attack
        {
          // Strategy 1: Group attack (Eq. 2)
          int na = naIni + (int)((naEnd - naIni) * u.RNDprobab ());
          GroupAttack (r, na);
        }
        else  // Chase
        {
          // Strategy 2: Chase (Eq. 3)
          Persecution (r);
        }
      }
      else  // Scavenging
      {
        // Strategy 3: Scavenging (Eq. 4)
        Scavenger (r);
      }
    }
  }
}
//————————————————————————————————————————————————————————————————————

The GroupAttack method implements "Strategy 1: Group attack". This strategy is used when a dingo decides to attack prey as part of a group. First, the auxiliary method CalculateAttackSum is called. It takes the current dingo's index (agentIdx) and the number of dingoes participating in the attack (na), and calculates the total strength or direction of the attack by summing the contributions from all participants. The result of this complex operation is stored in the sumAttack variable.

Next, the method updates the position (coordinates) of the current dingo. The method iterates over each 'c' coordinate of the search space and calculates a new value for the dingo's position. The updated formula is as follows:

New coordinate = Best solution coordinate + beta1 * total attack vector

where:

  • cB [c] represents the c-th coordinate of the current best solution found in the optimization,
  • beta1 — coefficient that regulates the intensity or direction of the group attack impact,
  • sumAttack — result obtained in the previous step.

The resulting new coordinate value is then "fitted" within the acceptable limits. The u.SeInDiSp method ensures that the new value remains within the specified minimum (rangeMin) and maximum (rangeMax) boundaries, while taking into account the step (rangeStep) of change. 

Ultimately, the GroupAttack method models a coordinated search for an optimal solution by a group of dingoes. It aggregates information from multiple individuals via sumAttack, takes into account the best solution found (cB) and uses a random coefficient beta1 to determine a new, potentially more advantageous, position for the dingo in the search space.

//————————————————————————————————————————————————————————————————————
//--- Strategy 1: Group attack (Eq. 2)
void C_AO_DOAm_dingo::GroupAttack (int agentIdx, int na)
{
  // Calculate the total attack vector
  double sumAttack = CalculateAttackSum (agentIdx, na);

  // Apply the group attack formula (Eq. 2)
  for (int c = 0; c < coords; c++)
  {
    // v = beta1 * sumatory - theBestVct
    a [agentIdx].c [c] = cB [c] + beta1 * sumAttack;
    a [agentIdx].c [c] = u.SeInDiSp (a [agentIdx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
//————————————————————————————————————————————————————————————————————

The CalculateAttackSum method is designed to calculate the total attack vector used in "Strategy 1: Group Attack". It determines how a particular dingo (agentIdx) will interact with other dingoes participating in a group attack (na).

First, the method creates a temporary list (attackVector) that will contain the indices of the dingoes directly involved in the attack. The size of this list is determined by the "na" parameter (number of attackers). Then the GetAttackVector helper method is called. This method, based on the current state of the population, selects "na" dingoes that will participate in the attack and writes their indices into the attackVector. A 'sumatory' variable is initialized to zero. Next comes the main loop, iterates over all dingoes involved in the attack: for each attacking dingo, whose index (attackerIdx) is taken from the attackVector.

Inside this loop there is another loop that goes through all coordinates (c) of the search space (loop: from "0" to "coords - 1"). At each coordinate, the difference between the position of the attacking dingo and the position of the dingo, for which the total attack is calculated, is defined. This difference is then divided by the total number of possible attack participants (na) and the total number of coordinates (coords), and added to 'sumatory'.

Finally, the method returns the 'sumatory' value. This value represents the average position difference between the attacking dingoes and the target dingo, adjusted for the number of participants and dimensions. This value will then be used to update the target dingo's position as part of a group attack. 

Thus, CalculateAttackSum collects information about the current positions of dingoes participating in a joint attack and outputs a complex value that reflects their total displacement relative to the attacked dingo. This value then directs the movement of the attacked dingo.

//————————————————————————————————————————————————————————————————————
//--- Calculate the sum for a group attack
double C_AO_DOAm_dingo::CalculateAttackSum (int agentIdx, int na)
{
  // Create a vector of attacking dingoes
  int attackVector [];
  ArrayResize (attackVector, na);
  GetAttackVector (na, attackVector);

  // Calculate the average difference in positions
  double sumatory = 0.0;
  for (int j = 0; j < na; j++)
  {
    int attackerIdx = attackVector [j];
    // Calculate the average over all coordinates
    for (int c = 0; c < coords; c++)
    {
      sumatory += (a [attackerIdx].c [c] - a [agentIdx].c [c]) / (na * coords);
    }
  }

  return sumatory;
}
//————————————————————————————————————————————————————————————————————

The GetAttackVector method is responsible for generating a list of dingoes that will directly participate in the group attack. It creates a set of random, unique indices, each corresponding to one dingo from the entire population. The method first sets the size of the attackVector output array to the given number "na" (the number of attackers). The 'c' counter is also initialized to zero. It will keep track of how many dingoes have already been selected for attack.

The method enters a loop that will continue until "na" dingoes are selected. In each iteration of the loop, a random index (idx) of dingo is generated from the entire available population (from "0" to "popSize - 1"). Before adding the generated index to the attackers' list, a check is performed. The method iterates through the already selected indices (from "0" to "c - 1") of the attackVector array. If the randomly generated index (idx) matches any of the previously chosen ones (i.e. attackVector[i] == idx), the "found" flag is set to 'true'. If the flag remains 'false' (that is, the given index has not yet been selected), then this new, unique index (idx) is added to the attackVector array under the current index of 'c'. After this, the counter 'c' is increased by one. 

Essentially, GetAttackVector is responsible for randomly selecting "na" unique dingoes from the entire population so that they can form an attack group. This process ensures that each dingo in the group is different from the others, preventing duplication and providing variety in the attack group.

//————————————————————————————————————————————————————————————————————
//--- Form the attacking dingo vector
void C_AO_DOAm_dingo::GetAttackVector (int na, int &attackVector [])
{
  int c = 0;
  ArrayResize (attackVector, na);

  while (c < na)
  {
    int idx = u.RNDintInRange (0, popSize - 1);

    // Check that the index is not repeated
    bool found = false;
    for (int i = 0; i < c; i++)
    {
      if (attackVector [i] == idx)
      {
        found = true;
        break;
      }
    }

    if (!found)
    {
      attackVector [c] = idx;
      c++;
    }
  }
}
//————————————————————————————————————————————————————————————————————

The Persecution method implements "Strategy 2: Chase". The goal of this method is to update the position of one specific dingo (agentIdx) so that it starts chasing. The method first selects a random dingo from the entire population, the index is stored in the r1 variable.

Next comes a cycle that goes through all the dimensions (c) of the space, in which the dingoes are located. For each dimension, the following formula is used to calculate the new position of the dingo (agentIdx): the product of three values is added to the base value (cB [c]), which represents the position of the best dingo: beta1 - a coefficient controlling the degree of chase influence on MathExp, (beta2) - an exponential function of the coefficient beta2 and the difference between the position of a selected random dingo (a [r1].c [c]), and the current position of the attacking dingo (a [agentIdx].c [c]) in the given dimension.

The new position is immediately adjusted using the normalization auxiliary function u.SeInDiSp, which ensures that the new position remains within the acceptable range from rangeMin[c] to rangeMax[c] for each dimension, taking into account the rangeStep[c] step. 

Ultimately, the Persecution method causes the specified dingo (agentIdx) to start moving towards a randomly selected other dingo (r1), while taking into account the base best position and using the beta1 and beta2 parameters to adjust the chase strength and dynamics. In this case, the new position always remains within the strict boundaries of the search space.

//————————————————————————————————————————————————————————————————————
//--- Strategy 2: Chase (Eq. 3)
void C_AO_DOAm_dingo::Persecution (int agentIdx)
{
  // Select a random dingo
  int r1 = u.RNDintInRange (0, popSize - 1);

  // Apply the chase formula (Eq. 3)
  for (int c = 0; c < coords; c++)
  {
    // v = theBestVct + beta1 * exp(beta2) * (Positions[r1] - Positions[r])
    a [agentIdx].c [c] = cB [c] + beta1 * MathExp (beta2) * (a [r1].c [c] - a [agentIdx].c [c]);
    a [agentIdx].c [c] = u.SeInDiSp (a [agentIdx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
//————————————————————————————————————————————————————————————————————

The Scavenger method implements "Strategy 3: Scavenging". The goal of this method is to update the position of one particular dingo (agentIdx) so that it moves to a new position that is some kind of "compromise" or "intermediate" value between its current position and the position of a randomly selected other dingo.

The method first selects a random dingo from the entire population and stores its index in the r1 variable. This dingo will act as a kind of reference point. A random binary value (sign) is also generated. This value will be either "1.0" or "-1.0". This determines whether the difference subtraction will be "forward" or "reverse".

Next comes the cycle, it goes through all the dimensions of the space the dingoes are located in. For each measurement, the new dingo position (agentIdx) is calculated as follows: the position of a randomly selected dingo (a[r1].c[c]) is taken and multiplied by the exponential of the coefficient "beta2", the current position of the attacking dingo is subtracted from this value (or added, depending on sign), and the resulting value is divided by 2.0. 

This effectively means that the new position will be somewhere between the dingo's current position and a new "attractive" position, depending on beta2 and the chosen dingo. As in other methods, after calculating the new position, it is immediately adjusted using the u.SeInDiSp auxiliary function. This function ensures that the new position stays within the allowed range (from rangeMin[c] to rangeMax[c]) for each dimension. 

Thus, the Scavenger method updates the dingo's position by moving it to a point that is the average between its current position and a position determined using an exponential transformation of the other dingo's position and a random sign. This mimics the behavior of an animal searching for prey that may be "along the way" or "somewhere between" known points.

//————————————————————————————————————————————————————————————————————
//--- Strategy 3: Scavenging (Eq. 4)
void C_AO_DOAm_dingo::Scavenger (int agentIdx)
{
  // Select a random dingo
  int r1 = u.RNDintInRange (0, popSize - 1);
  double sign = u.RNDbool() ? 1.0 : -1.0;

  // Apply the scavenging formula (Eq. 4)
  for (int c = 0; c < coords; c++)
  {
    // v = (exp(beta2) * Positions[r1] - (-1)^binary * Positions[r]) / 2
    a [agentIdx].c [c] = (MathExp (beta2) * a [r1].c [c] - sign * a [agentIdx].c [c]) / 2.0;
    a [agentIdx].c [c] = u.SeInDiSp (a [agentIdx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
//————————————————————————————————————————————————————————————————————

The SurvivalProcedure method implements "Strategy 4: Survival procedure". The main goal of this method is to update the position of one specific dingo (agentIdx) using information from two other dingoes to find a new, more advantageous position. The method first selects two different random dingoes from the entire population. Their indices are stored in r1 and r2 variables. It is guaranteed that r1 and r2 are not the same.

A random binary value (sign) is also generated, which can be either "1.0" or "-1.0". It determines whether the position of the second dingo will be subtracted with the direct or inverted sign. Next, a loop follows through all the dimensions. For each dimension, a new dingo position is calculated as follows: half the difference between the position of the first random dingo (a[r1].c[c]) and the position of the second random dingo (a[r2] is added to the best position found (cB[c]). c[c]), while the sign of the position of the second dingo can be inverted depending on "sign". After calculating the new position, it should be adjusted using the SeInDiSp auxiliary function. 

In general, the SurvivalProcedure method demonstrates a strategy where a dingo updates its position by calculating it as a variation from the best position found, adding to it half the difference between the positions of two other randomly selected dingoes. The random sign adds an element of unpredictability to the choice of direction for this difference, and the position limitation ensures that the dingo stays in its working area. This can be interpreted as an attempt to find a "mean" between two other potential "promising" points, while taking into account the best known position.

//————————————————————————————————————————————————————————————————————
//--- Strategy 4: Survival procedure (Eq. 6)
void C_AO_DOAm_dingo::SurvivalProcedure (int agentIdx)
{
  // Select two different random dingoes
  int r1, r2;
  do
  {
    r1 = u.RNDintInRange (0, popSize - 1);
    r2 = u.RNDintInRange (0, popSize - 1);
  }
  while (r1 == r2);

  double sign = u.RNDbool() ? 1.0 : -1.0;

  // Apply the survival formula (Eq. 6)
  for (int c = 0; c < coords; c++)
  {
    // v = theBestVct + (Positions[r1] - (-1)^binary * Positions[r2]) / 2
    a [agentIdx].c [c] = cB [c] + (a [r1].c [c] - sign * a [r2].c [c]) / 2.0;
    a [agentIdx].c [c] = u.SeInDiSp (a [agentIdx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
//————————————————————————————————————————————————————————————————————

The UpdateSurvivalRates method is responsible for calculating and updating the "survival rates" for each dingo in the population. These indicators determine the likelihood that a dingo will help determine agent behavior in the next iteration.

First, the method initializes two variables: minFitness and maxFitness. It then cycles through all dingoes in the population. For each dingo, its current fitness value (a[i].f) is compared with the current minFitness and maxFitness. If the current fitness is less than minFitness, minFitness is updated. If the current fitness is greater than maxFitness, maxFitness is updated. As a result of this cycle, minFitness will contain the smallest fitness value in the entire population, and maxFitness will contain the largest.

If the difference between maxFitness and minFitness is very small (practically zero, meaning that all dingoes have the same fitness), then all dingoes are assigned a survival value of "0.5". This prevents division by zero and establishes equal chances for everyone.

If the fitness of the dingoes differs, the method is repeated in a loop across all dingoes. For each dingo, its survival rate (survival [i]) is calculated. The formula for this indicator is as follows: (maxFitness - a [i].f) / (maxFitness - minFitness). This formula normalizes the dingo's fitness value to the range of all fitness values.

The dingo with the lowest fitness, close to minFitness, will have a survival rate close to 1.0 (since maxFitness - a[i].f will be large). A dingo with the highest fitness, close to maxFitness, will have a survival rate close to 0.0 (since maxFitness - a[i].f will be close to zero).

Ultimately, the UpdateSurvivalRates method transforms absolute dingo fitness values into relative "survival rates", where higher fitness values correspond to lower survival rates, and vice versa. This can be interpreted as a strategy where those who "suffer" (have low fitness) have a greater chance of "surviving" or changing, while those who are most "successful" (with high fitness) have less incentive to change.

//————————————————————————————————————————————————————————————————————
//--- Update survival rates
void C_AO_DOAm_dingo::UpdateSurvivalRates ()
{
  // Find the minimum and maximum 'fitness'
  double minFitness =  DBL_MAX;
  double maxFitness = -DBL_MAX;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f < minFitness) minFitness = a [i].f;
    if (a [i].f > maxFitness) maxFitness = a [i].f;
  }

  // Calculate survival rates
  if (MathAbs (maxFitness - minFitness) < DBL_EPSILON)
  {
    ArrayInitialize (survival, 0.5);
  }
  else
  {
    for (int i = 0; i < popSize; i++)
    {
      // survival_rate = (max - fit) / (max - min)
      survival [i] = (maxFitness - a [i].f) / (maxFitness - minFitness);
    }
  }
}
//————————————————————————————————————————————————————————————————————

The Revision method focuses on finding the best dingo in the current population after sorting and, if this dingo outperforms previously known best results, updates the global best solution scores. This is a standard procedure for optimization algorithms to store and track the best solution found throughout the search.

//————————————————————————————————————————————————————————————————————
//--- Update the best and worst solutions
void C_AO_DOAm_dingo::Revision ()
{
  // Sort the population to find the best and worst
  static S_AO_Agent aT [];
  ArrayResize (aT, popSize);
  u.Sorting (a, aT, popSize);

  // Update the global best solution
  if (a [0].f > fB)
  {
    fB = a [0].f;
    ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY);
  }
}
//————————————————————————————————————————————————————————————————————


Test results

Results from 50 runs, instead of the usual 10 (to smooth out unusually strong but random outcomes)

DOA|Dingo Optimization Algorithm|50.0|0.5|0.7|
=============================
5 Hilly's; Func runs: 10000; result: 0.4796838225601811
25 Hilly's; Func runs: 10000; result: 0.4536754883347326
500 Hilly's; Func runs: 10000; result: 0.4636906699500103
=============================
5 Forest's; Func runs: 10000; result: 0.9414536230352631
25 Forest's; Func runs: 10000; result: 0.8790978288567921
500 Forest's; Func runs: 10000; result: 0.9145376343407303
=============================
5 Megacity's; Func runs: 10000; result: 0.7861538461538461
25 Megacity's; Func runs: 10000; result: 0.8606153846153848
500 Megacity's; Func runs: 10000; result: 0.8480461538461537
=============================
All score: 6.62695 (73.63%)

The visualization shows a complex changing pattern in the search area, associated with the use of different hunting strategies (the characteristic diagonal patterns inherited from its predecessor are still visible). However, despite the high overall scores, there is a very large spread in the results across all test functions.

Hilly

DOAm_dingo on the Hilly test function

Forest

DOAm_dingo on the Forest test function

Megacity

DOAm_dingo on the Megacity test function

Based on the test results, the DOAm_dingo algorithm ranks first in our ranking of population optimization methods.

# 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)
1DOAm_dingodingo_optimization_algorithm_M0.479680.453670.463691.397040.941450.879090.914542.735080.786150.860610.848052.494816.62773.63
1ANSacross neighbourhood search0.949480.847760.438572.235811.000000.923340.399882.323230.709230.634770.230911.574916.13468.15
2CLAcode lock algorithm (joo)0.953450.871070.375902.200420.989420.917090.316422.222940.796920.693850.193031.683806.10767.86
3AMOmanimal migration ptimization M0.903580.843170.462842.209590.990010.924360.465982.380340.567690.591320.237731.396755.98766.52
4(P+O)ES(P+O) evolution strategies0.922560.881010.400212.203790.977500.874900.319452.171850.673850.629850.186341.490035.86665.17
5CTAcomet tail algorithm (joo)0.953460.863190.277702.094350.997940.857400.339492.194840.887690.564310.105121.557125.84664.96
6TETAtime evolution travel algorithm (joo)0.913620.823490.319902.057010.970960.895320.293242.159520.734620.685690.160211.580525.79764.41
7SDSmstochastic diffusion search M0.930660.854450.394762.179880.999830.892440.196192.088460.723330.611000.106701.441035.70963.44
8BOAmbilliards optimization algorithm M0.957570.825990.252352.035901.000000.900360.305022.205380.735380.525230.095631.356255.59862.19
9AAmarchery algorithm M0.917440.708760.421602.047800.925270.758020.353282.036570.673850.552000.237381.463235.54861.64
10ESGevolution of social groups (joo)0.999060.796540.350562.146161.000000.828630.131021.959650.823330.553000.047251.423585.52961.44
11SIAsimulated isotropic annealing (joo)0.957840.842640.414652.215130.982390.795860.205071.983320.686670.493000.090531.270205.46960.76
12EOmextremal_optimization_M0.761660.772420.317471.851550.999990.767510.235272.002770.747690.539690.142491.429875.28458.71
13BBObiogeography based optimization0.949120.694560.350311.993990.938200.673650.256821.868670.746150.482770.173691.402615.26558.50
14ACSartificial cooperative search0.755470.747440.304071.806981.000000.888610.224132.112740.690770.481850.133221.305835.22658.06
15DAdialectical algorithm0.861830.700330.337241.899400.981630.727720.287181.996530.703080.452920.163671.319675.21657.95
16BHAmblack hole algorithm M0.752360.766750.345831.864930.935930.801520.271772.009230.650770.516460.154721.321955.19657.73
17ASOanarchy society optimization0.848720.746460.314651.909830.961480.791500.238031.991010.570770.540620.166141.277525.17857.54
18RFOroyal flush optimization (joo)0.833610.737420.346291.917330.894240.738240.240981.873460.631540.502920.164211.298675.08956.55
19AOSmatomic orbital search M0.802320.704490.310211.817020.856600.694510.219961.771070.746150.528620.143581.418355.00655.63
20TSEAturtle shell evolution algorithm (joo)0.967980.644800.296721.909490.994490.619810.227081.841390.690770.426460.135981.253225.00455.60
21BSAbacktracking_search_algorithm0.973090.545340.290981.809410.999990.585430.217471.802890.847690.369530.129781.347004.95955.10
22DEdifferential evolution0.950440.616740.303081.870260.953170.788960.166521.908650.786670.360330.029531.176534.95555.06
23SRAsuccessful restaurateur algorithm (joo)0.968830.634550.292171.895550.946370.555060.191241.692670.749230.440310.125261.314804.90354.48
24CROchemical reaction optimization0.946290.661120.298531.905930.879060.584220.211461.674730.758460.426460.126861.311784.89254.36
25BIOblood inheritance optimization (joo)0.815680.653360.308771.777810.899370.653190.217601.770160.678460.476310.139021.293784.84253.80
26DOAdream_optimization_algorithm0.855560.700850.372801.929210.734210.489050.241471.464730.772310.473540.185611.431464.82553.62
27BSAbird swarm algorithm0.893060.649000.262501.804550.924200.711210.249391.884790.693850.326150.100121.120124.80953.44
28DEAdolphin_echolocation_algorithm0.759950.675720.341711.777380.895820.642230.239411.777460.615380.440310.151151.206844.76252.91
29HSharmony search0.865090.687820.325271.878180.999990.680020.095901.775920.620000.422670.054581.097254.75152.79
30SSGsaplings sowing and growing0.778390.649250.395431.823080.859730.624670.174291.658690.646670.441330.105981.193984.67651.95
31BCOmbacterial chemotaxis optimization M0.759530.622680.314831.697040.893780.613390.225421.732590.653850.420920.144351.219124.64951.65
32ABOafrican buffalo optimization0.833370.622470.299641.755480.921700.586180.197231.705110.610000.431540.132251.173784.63451.49
33(PO)ES(PO) evolution strategies0.790250.626470.429351.846060.876160.609430.195911.681510.590000.379330.113221.082554.61051.22
34FBAfractal-based Algorithm0.790000.651340.289651.730990.871580.568230.188771.628580.610770.460620.123981.195374.55550.61
35TSmtabu search M0.877950.614310.291041.783300.928850.518440.190541.637830.610770.382150.121571.114494.53650.40
36BSObrain storm optimization0.937360.576160.296881.810410.931310.558660.235371.725340.552310.290770.119140.962224.49849.98
37WOAmwale optimization algorithm M0.845210.562980.262631.670810.931000.522780.163651.617430.663080.411380.113571.188034.47649.74
38AEFAartificial electric field algorithm0.877000.617530.252351.746880.927290.726980.180641.834900.666150.116310.095080.877544.45949.55
39AEOartificial ecosystem-based optimization algorithm0.913800.467130.264701.645630.902230.437050.214001.553270.661540.308000.285631.255174.45449.49
40CAmcamel algorithm M0.786840.560420.351331.698590.827720.560410.243361.631490.648460.330920.134181.113564.44449.37
41ACOmant colony optimization M0.881900.661270.303771.846930.858730.586800.150511.596040.596670.373330.024720.994724.43849.31
42CMAEScovariance_matrix_adaptation_evolution_strategy0.762580.720890.000001.483470.820560.796160.000001.616720.758460.490770.000001.249234.34948.33
43DA_duelistduelist_algorithm0.927820.537780.277921.743520.869570.475360.181931.526860.621530.335690.117151.074374.34548.28
44BFO-GAbacterial foraging optimization - ga0.891500.551110.315291.757900.969820.396120.063051.428990.726670.275000.035251.036924.22446.93
RWrandom walk0.487540.321590.257811.066940.375540.219440.158770.753750.279690.149170.098470.527342.34826.09


Summary

The considered modification of the DOA_dingo algorithm stands out for its high search capabilities. However, it should be noted that these impressive results are average values obtained in a series of tests. This means that to ensure sufficient exploration of the search space, the user will have to perform the optimization multiple times.

On the other hand, the algorithm shows a rather modest convergence rate on smooth functions. Nevertheless, DOA_dingo is a very useful and promising method. I would recommend using it for the initial stage of searching, with subsequent transition to more accurate algorithms. It is also extremely important to pre-normalize all parameters to the same range of values.

tab

Figure 2. Color-coded comparison of algorithms across 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)

DOAm_dingo pros and cons:

Pros:

  1. Fast.
  2. Very high average results.

Disadvantages:

  1. High variability in results.
  2. Tendency to get stuck.

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

#NameTypeDescription
1#C_AO.mqh
Include
Parent class of population optimization algorithms
2#C_AO_enum.mqh
Include
Enumeration of population optimization algorithms
3TestFunctions.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 for the comparison table
7
Testing AOs.mq5
ScriptThe 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_DOAm_dingo.mq5
ScriptDOAm_dingo test stand

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

Attached files |
DOAm_Dingo.zip (279.94 KB)
Last comments | Go to discussion (2)
Yevgeniy Koshtenko
Yevgeniy Koshtenko | 23 Sep 2025 at 18:39
An excellent algorithm! Unbelievable
cemal
cemal | 25 Jul 2026 at 10:17
How is this algorithm can be ued for trading ?Can you give some regression or classificition exapmples?
Defining your Edge (Part 1): Using a Discrete Fourier Transform and a Spiking Neural Network in a Trading Robot Defining your Edge (Part 1): Using a Discrete Fourier Transform and a Spiking Neural Network in a Trading Robot
In this article we make the case for pairing the Discrete Fourier Transform with a Spiking Neural Network in a Trading Robot. The Fourier Transform helps represent data as oscillations instead of its raw values. To govern how we interpret these cycles, we engage a Spiking Neural Network that unlike regular networks, uses time dependent electrical charges to accumulate potential and only "spike" when a target threshold is met. Combining these two engines allows us better control on the timing of discrete market movements, that in theory should give us entry signals with rigorous mathematical confirmation.
Building a Correlation-Aware Portfolio Risk Monitor in MQL5 Building a Correlation-Aware Portfolio Risk Monitor in MQL5
The article quantifies correlation and portfolio risk in MetaTrader 5: from time-aligned returns to a covariance matrix, true portfolio variance against the independent-sum assumption, and position-level risk attribution. A MetaTrader 5 service runs in the background, shows the metrics on a small chart panel, and pushes alerts when risk thresholds are crossed. Source code is provided for an example script, a reusable risk engine class, and the service.
Comparing Trade Return Distributions with Mann-Whitney U in MQL5 Comparing Trade Return Distributions with Mann-Whitney U in MQL5
A native, dependency-free MQL5 implementation of the Mann-Whitney U test for comparing trade returns across two market regimes. It details rank calculation, tie correction, and a normal-approximation p-value, and pairs the test with a CCanvas box-and-whisker chart and a trade-history extraction script. A verification script is included, and the limits of the normal approximation and independence assumptions are clearly stated for informed use.
Price Action Analysis Toolkit Development (Part 75): Building a Modular Multi-Symbol Trading Panel in MQL5 Price Action Analysis Toolkit Development (Part 75): Building a Modular Multi-Symbol Trading Panel in MQL5
A structured MQL5 implementation of a multi‑symbol trading panel with clear separation of concerns: symbol handling, trading logic, and GUI. Integrated into an Expert Advisor, it validates symbols, exposes centralized controls for opening and positions managing across symbols, and applies SL/TP changes. Real‑time account and portfolio metrics help streamline routine operations from a single chart.