Русский Português
preview
Duelist Algorithm

Duelist Algorithm

MetaTrader 5Trading |
1 408 2
Andrey Dik
Andrey Dik

Contents

  1. Introduction
  2. Algorithm implementation
  3. Test results


Introduction

In algorithmic trading, every millisecond means profit or loss, so finding the optimal parameters for a trading strategy becomes a critical task. Modern traders are turning to artificial intelligence and evolutionary algorithms to fine-tune their trading systems.

Today we will look at a new optimization approach — the Duelist Algorithm — that draws inspiration from the ancient practice of dueling. The algorithm was developed in 2015 by a group of Indonesian scientists led by Biyanto as an alternative to traditional evolutionary algorithms with the aim of minimizing the "blind" nature of mutation and crossover operators through a differentiated approach to winners and losers. In this article, we will examine the mathematical basis of the Duelist Algorithm in detail, implement it in MQL5, and conduct a comparative analysis with other population-based optimization methods.


Algorithm implementation

Think of the trading floor as an arena where different strategies constantly compete with each other. Some strategies win in certain market conditions, while others fail. But what makes a successful trader? The ability to learn from both one's victories and defeats, constantly adapting and refining one's approach.

This is precisely the concept the Duelist Algorithm embodies. Unlike classical genetic algorithms, where all individuals are treated equally, the duelist algorithm recognizes a fundamental difference between winners and losers. Losers learn — they analyze the strategies of winners and adopt their successful elements, while winners innovate — they experiment with new approaches, knowing that their core strategy has already proven its efficiency. Champions mentor – the best strategies are passed on.

illustration

Figure 1. DA_duelist algorithm in action

The visualization of the main stages of the algorithm demonstrates all the key aspects of the Duelist Algorithm: the initial population - all duelists start with equal chances; champion determination - the best duelists are highlighted in gold. Duels - the process of a duel with the determination of a winner and a loser is presented. Learning and innovation - two different development paths for losers and winners. Training new duelists, where champions pass on their skills to the next generation, and elimination - the worst duelists are removed from the population.

The illustration also includes a loop arrow showing the iterative nature of the algorithm, the final result, highlighting the champion, and a detailed legend explaining all the elements and key parameters. The visualization helps the reader quickly grasp the core idea of the algorithm.

Now, after a detailed analysis, let's write the algorithm pseudocode.

INPUTS:
- popSize: size of the duelist population
- luckCoefficient: luck coefficient in duels
- learningProbability: probability of the loser learning
- innovationProbability: probability of a winner innovation
- championsCount: number of champions

INITIALIZATION:
1. Check and adjust championsCount:
   - If < 1, set = 1
   - If >= popSize, set = popSize / 4
2. Create empty 'winners' and 'losers' arrays
3. Create an array named 'champions' of size championsCount

MAIN CYCLE (Moving):

IF first iteration:
   FOR each duelist i from 0 to popSize:
      FOR each coordinate j:
         Initialize with a random value within allowed bounds
   Set the 'revision' flag = true
   EXIT from the function

ELSE (subsequent iterations):
   1. POPULATION EXPANSION:
      - Increase the duelists array to the size (popSize + championsCount)
      - Initialize the structures of new duelists

   2. DETERMINING THE CHAMPIONS:
      - Sort duelists by fitness level (bubble sort)
      - The first championsCount of duelists become champions
      
   3. TRAINING NEW DUELISTS:
      FOR each champion i:
         Call TrainNewDuelist (i, popSize + i):
            FOR each coordinate c:
               new_duelist [c] = champion [c] + GaussDistribution ()
               Apply range restrictions

   4. CONDUCTING DUELS:
      Clear the 'winners' and 'losers' arrays
      FOR each duelist i from championsCount to totalDuelists:
         Select a random 'opponent'
         IF opponent != i:
            DetermineWinnerAndLoser(i, opponent):
               A_Luck = fitness [A] * (luckCoef + random () * luckCoef)
               B_Luck = fitness [B] * (luckCoef + random () * luckCoef)
               IF (fitness [A] + A_Luck) >= (fitness [B] + B_Luck):
                  A - winner, B - loser
               OTHERWISE:
                  B - winner, A - loser
               Add 'winners' and 'losers' to the arrays

   5. IMPROVEMENT:
      a) Learning phase for losers:
         FOR each pair (loser, winner):
            LearningProcess (loser, winner):
               FOR each coordinate c:
                  IF random () < learningProbability:
                     loser [c] = winner [c]

      b) Winner innovations:
         FOR each winner:
            InnovationProcess (winner):
               FOR each coordinate c:
                  IF random () < innovationProbability:
                     winner [c] = random_value_in_range

   6. ELIMINATION:
      - Sort all duelists in descending order by 'fitness'
- Leave only the first popSize duelists

UPDATE (Revision):
   FOR each duelist i:
      IF fitness [i] > global_best:
         Update global_best = fitness [i]
         Save the coordinates of the best solution

REPEAT the main loop until the stopping criterion is reached

I would like to highlight the key features of this algorithm, which are that champions do not participate in duels, but rather train new duelists, and the element of randomness through the luck coefficient makes duels unpredictable. Differentiated approach: losers learn, and winners seek innovative solutions. The population size remains constant through elimination of the worst-performing duelists. Now we can move on to the implementation of the DA_duelist algorithm code.

Let's write the C_AO_DA_duelist class, which inherits the C_AO class, initializes an array of parameters (params) and assigns them names and default values. 

  • SetParams () — changes the values of internal variables (popSize, luckCoefficient, etc.) based on the values stored in the "params" array. 
  • Init () — algorithm initialization method. It accepts arrays to define ranges and steps for parameters, as well as the number of epochs. 
  • Moving ()— a method that contains the main logic for moving the "duelists" (searching for optimal parameters). 
  • Revision () — responsible for analyzing results and making adjustments to the process.

Variables:

  • luckCoefficient — luck coefficient;
  • learningProbability — learning probability;
  • innovationProbability — innovation probability;
  • championsCount — number of champions;
  • winners [] — array of indices of winning "duelists";
  • losers [] — array of indices of the losing "duelists";
  • champions [] — array of "champions" indices.
Private methods:
  • DetermineWinnerAndLoser() determines the winner and loser of a "duel";
  • LearningProcess () implements the learning process for the loser using the winner;
  • InnovationProcess () implements the innovation process for the winner;
  • TrainNewDuelist () trains a new "duelist" based on the champion.
//————————————————————————————————————————————————————————————————————
class C_AO_DA_duelist : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_DA_duelist () { }
  C_AO_DA_duelist ()
  {
    ao_name = "DA";
    ao_desc = "Duelist Algorithm";
    ao_link = "https://www.mql5.com/en/articles/19093";

    popSize               = 50;    // number of duelists
    luckCoefficient       = 0.01;  // luck coefficient
    learningProbability   = 0.2;   // learning probability for losers
    innovationProbability = 0.1;   // probability of innovation for winners
    championsCount        = 5;     // number of champions

    ArrayResize (params, 5);

    params [0].name = "popSize";               params [0].val = popSize;
    params [1].name = "luckCoefficient";       params [1].val = luckCoefficient;
    params [2].name = "learningProbability";   params [2].val = learningProbability;
    params [3].name = "innovationProbability"; params [3].val = innovationProbability;
    params [4].name = "championsCount";        params [4].val = championsCount;
  }

  void SetParams ()
  {
    popSize               = (int)params [0].val;
    luckCoefficient       = params      [1].val;
    learningProbability   = params      [2].val;
    innovationProbability = params      [3].val;
    championsCount        = (int)params [4].val;
  }

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  double luckCoefficient;       // luck coefficient
  double learningProbability;   // learning probability
  double innovationProbability; // innovation probability
  int    championsCount;        // number of champions

  private: //---------------------------------------------------------
  int    winners [];            // winners' indices
  int    losers  [];            // losers' indices
  int    champions [];          // champions' indices

  void   DetermineWinnerAndLoser (int duelistA, int duelistB);
  void   LearningProcess         (int loserIndex, int winnerIndex);
  void   InnovationProcess       (int winnerIndex);
  void   TrainNewDuelist         (int championIndex, int newDuelistIndex);
};
//————————————————————————————————————————————————————————————————————

The Init method of the C_AO_DA_duelist class is intended to prepare the algorithm for execution. Its main task is to initialize the initial parameters and data structures before starting work.

The method starts by calling standard initialization, which handles ranges and steps for the parameters. If this standard initialization fails, the method returns 'false' indicating that it cannot continue. The method then adjusts the number of champions: if it is less than one, it sets it to one; if it is greater than or equal to the population size, it reduces it to a quarter of the total population size. The arrays storing the indices of winners, losers, and champions are then cleared and redistributed so that they are empty and ready for the next stage of the algorithm. At the end, the method returns 'true', signaling successful initialization.

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

  //------------------------------------------------------------------
  if (championsCount < 1) championsCount = 1;
  if (championsCount >= popSize) championsCount = popSize / 4;

  ArrayResize (winners, 0);
  ArrayResize (losers,  0);
  ArrayResize (champions, championsCount);

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

DetermineWinnerAndLoser method of the C_AO_DA_duelist class determines the winner and loser in a duel between two individuals (duelists) of the population. Let's consider the order of actions.

  1. "Luck" calculation. For each duelist (A and B), the "Luck" value (A_Luck and B_Luck) is calculated. "Luck" depends on the duelist's base f value (their "fitness"), the luckCoefficient coefficient, and a random element obtained using u.RNDprobab(). Some randomness is used to simulate the effect of luck.
  2. Determining the winner. The sum of the duelist's f fitness and its "luck" is compared. Whichever duelist has the greater or equal resulting value is considered the winner. 
  3. Saving results. The winner and loser indices are added to the corresponding 'winners' and 'losers' arrays. These arrays are used for further analysis and population evolution.

    //————————————————————————————————————————————————————————————————————
    //--- Determine the winner and loser in a duel
    void C_AO_DA_duelist::DetermineWinnerAndLoser (int duelistA, int duelistB)
    {
      // Algorithm from the document
      double A_Luck = a [duelistA].f * (luckCoefficient + u.RNDprobab () * luckCoefficient);
      double B_Luck = a [duelistB].f * (luckCoefficient + u.RNDprobab () * luckCoefficient);
    
      if ((a [duelistA].f + A_Luck) >= (a [duelistB].f + B_Luck))
      {
        ArrayResize (winners, ArraySize (winners) + 1);
        ArrayResize (losers,  ArraySize (losers) + 1);
        winners [ArraySize (winners) - 1] = duelistA;
        losers  [ArraySize (losers) - 1] = duelistB;
      }
      else
      {
        ArrayResize (winners, ArraySize (winners) + 1);
        ArrayResize (losers,  ArraySize (losers) + 1);
        winners [ArraySize (winners) - 1] = duelistB;
        losers  [ArraySize (losers) - 1] = duelistA;
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The LearningProcess method of the C_AO_DA_duelist class implements a learning process, in which an individual that loses a duel "learns" from the winner. The purpose of this process is to improve the loser's performance by transferring some of the winner's strategy to the losing individual. Let's see what happens inside the method:

    Iteration over coordinates. The "for" loop iterates over all the "coordinates" of an individual. The number of coordinates is determined by the "coords" variable.

    Probabilistic copying. Inside the loop, a random check is performed using u.RNDprobab (). If the random number is less than learningProbability, the following action occurs.

    Copying characteristics. A loser (loserIndex) "borrows" the value of the c coordinate from the winner (winnerIndex). This means that the losing individual's characteristic is updated to take the value of the corresponding characteristic of the winner. Essentially, the loser copies part of the winner's strategy.

    Ultimately, the method simulates the transfer of knowledge or strategy from a more successful individual to a less successful one, which is the basis for the evolutionary process.

    //————————————————————————————————————————————————————————————————————
    //--- A loser learns from a winner
    void C_AO_DA_duelist::LearningProcess (int loserIndex, int winnerIndex)
    {
      for (int c = 0; c < coords; c++)
      {
        if (u.RNDprobab () < learningProbability)
        {
          // A loser copies part of a winner's strategy
          a [loserIndex].c [c] = a [winnerIndex].c [c];
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The InnovationProcess method of the C_AO_DA_duelist class implements the process of "innovation" or mutation for the individual that wins the duel. This process introduces random changes to the winner's strategy, exploring new possibilities.

    Iteration over coordinates. The "for" loop iterates over all the 'c' coordinates of an individual, similar to LearningProcess.

    Probabilistic mutation. If the random number generated by u.RNDprobab() is less than innovationProbability, a mutation occurs. In other words, an individual tries to change its strategy with a given chance (set by innovationProbability).

    Generation and correction of a new value. The random number generator u.RNDfromCI () creates a random value for the 'c' parameter that is in the range from rangeMin[c] to rangeMax[c]. The resulting random value is adjusted using u.SeInDiSp(). This function is responsible for converting a value to a discrete set of values with a step rangeStep [c] within a given range (rangeMin [c], rangeMax [c]).

    As a result, the method allows the winner to experiment with new strategies, which can potentially lead to improved characteristics of the individual and its adaptability to the environment. This is an important part of the evolutionary process.

    //————————————————————————————————————————————————————————————————————
    //--- The innovation process for a winner
    void C_AO_DA_duelist::InnovationProcess (int winnerIndex)
    {
      for (int c = 0; c < coords; c++)
      {
        if (u.RNDprobab () < innovationProbability)
        {
          // The winner tries a new technique (mutation)
          a [winnerIndex].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
          a [winnerIndex].c [c] = u.SeInDiSp (a [winnerIndex].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The TrainNewDuelist method of the C_AO_DA_duelist class is responsible for creating a new duelist (individual) and its initial setup by "training" it from the current champion. It is a process of inheritance, but with elements of randomness, which allows for the introduction of genetic diversity into the population.

    Iteration over coordinates. The "for" loop iterates over all the 'c' parameters (coordinates) of the new duelist (newDuelistIndex), setting each of them.

    Calculating deviation. The 'deviation' is calculated, which determines the range of possible changes relative to the spread of acceptable values.

    Inheritance with mutation (Gauss Distribution). A random deviation obtained using the u.GaussDistribution() function is added to the champion 'c' characteristic (championIndex). This function generates a random number from a normal distribution (Gaussian distribution), which allows for random variations in inherited characteristics. The arguments 0, rangeMin [c], rangeMax [c], 8 specify the parameters of the normal distribution: the mean value 0, the minimum and maximum values, and the parameter controlling the width of the distribution (8).

    Adjustment to the discrete grid (SeInDiSp). The resulting value of the new duelist's characteristic after mutation is converted to a discrete set of values using the u.SeInDiSp() function. This function ensures that the new duelist's parameters are within the allowed range and correspond to the rangeStep[c] step.

    As a result, the new duelist receives stats similar to those of the champion, but with random variations, which creates diversity in the population and allows for the exploration of new strategies. 

    //————————————————————————————————————————————————————————————————————
    //--- A champion trains a new duelist
    void C_AO_DA_duelist::TrainNewDuelist (int championIndex, int newDuelistIndex)
    {
      for (int c = 0; c < coords; c++)
      {
        // The new duelist inherits the champion's abilities with slight variations
        double deviation = (rangeMax [c] - rangeMin [c]) * 0.1;
        a [newDuelistIndex].c [c] = a [championIndex].c [c] + u.GaussDistribution (0, rangeMin [c], rangeMax [c], 8);
        a [newDuelistIndex].c [c] = u.SeInDiSp (a [newDuelistIndex].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    The Moving method of the C_AO_DA_duelist class represents the main step of the duelist evolution algorithm. It includes initialization (at first launch), reproduction, selection, duels, training (using the results of duels), innovation and culling. This is the heart of the evolutionary process that happens inside the method:

    Initialization of the population (first run). If this is the first run (revision is 'false'), the population is initialized: for each individual i, values for all characteristics j are randomly assigned according to the allowed range and step. After initialization, 'revision' is set to 'true' to avoid reinitialization on subsequent runs.

    Preparing for reproduction. The size of 'a' array (representing the population) increases to accommodate new duelists spawned from champions. New duelists are initialized (initial states are set). 

    Selecting champions. The population is sorted by the f (fitness) parameter to select the best individuals. The champions are determined - the best championsCount individuals. Each champion uses the TrainNewDuelist method to create a new duelist that inherits the champion's traits.

    Preparing for duels. The "winners" and "losers" arrays are cleared to prepare for new duel results. 

    Conducting duels. Each individual, except champions, fights one random opponent (also not a champion). The outcome of each duel (who wins, who loses) is determined using DetermineWinnerAndLoser.

    Duelist training (Learning). Losers learn from winners. LearningProcess allows losers to change their parameters by adopting the best traits of winners. 

    Innovations of winners. The best individuals use InnovationProcess to try to change their strategy and explore new possibilities.

    Re-sorting. The entire population is sorted again by 'f' parameter to update the order of individuals after changes made through duels, training, and innovation. 

    Culling the worst. The size of the 'a' array is reset to popSize, removing the worst individuals to maintain a constant population size.

    This cycle represents one step of evolution, in which offspring are created, the best are selected, learning occurs, strategies are updated, and the population size is maintained. The Moving method performs the basic evolutionary operations: reproduction, mutation, selection and learning.

    //————————————————————————————————————————————————————————————————————
    //--- The main step of the algorithm
    void C_AO_DA_duelist::Moving ()
    {
      // Initial population setup
      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;
      }
    
      //------------------------------------------------------------------
      // Temporary array expansion for new duelists
      int totalDuelists = popSize + championsCount;
      ArrayResize (a, totalDuelists);
    
      // Initialize new duelists
      for (int i = popSize; i < totalDuelists; i++)
      {
        a [i].Init (coords);
      }
    
      // Sort to determine champions (using bubble sort)
      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)
          {
            S_AO_Agent temp = a [j];
            a [j] = a [j + 1];
            a [j + 1] = temp;
          }
        }
      }
    
      // Determine the champions
      for (int i = 0; i < championsCount; i++)
      {
        champions [i] = i;
        // A champion trains a new duelist
        TrainNewDuelist (i, popSize + i);
      }
    
      // Clear the winners' and losers' arrays
      ArrayResize (winners, 0);
      ArrayResize (losers,  0);
    
      // Conduct duels (excluding champions)
      for (int i = championsCount; i < totalDuelists; i++)
      {
        // Each duelist fights one random opponent
        int opponent = u.RNDintInRange (championsCount, totalDuelists - 1);
        if (opponent != i)
        {
          DetermineWinnerAndLoser (i, opponent);
        }
      }
    
      // Improving duelists
      int minCount = MathMin (ArraySize (winners), ArraySize (losers));
      for (int i = 0; i < minCount; i++)
      {
        // Losers learn from winners
        LearningProcess (losers [i], winners [i]);
      }
    
      for (int i = 0; i < ArraySize (winners); i++)
      {
        // Winners innovate
        InnovationProcess (winners [i]);
      }
    
      // Sort all duelists
      for (int i = 0; i < totalDuelists - 1; i++)
      {
        for (int j = 0; j < totalDuelists - i - 1; j++)
        {
          if (a [j].f < a [j + 1].f)
          {
            S_AO_Agent temp = a [j];
            a [j] = a [j + 1];
            a [j + 1] = temp;
          }
        }
      }
    
      // Remove the worst duelists
      ArrayResize (a, popSize);
    }
    //————————————————————————————————————————————————————————————————————
    

    The Revision method of the C_AO_DA_duelist class is used to update information about the best solution found in the current population. It is designed to track the global optimum during the evolution.

    Iteration over the population. The "for" loop iterates over each individual in the population (i from 0 to popSize). 

    Fitness comparison. Inside the loop, we check whether the 'f' fitness of the current individual is better than the current best fitness of fB.

    Update the best solution. If (a[i].f) is better than (fB), then (fB) is updated, that is, (fB) remembers the best fitness found so far. In addition, the 'c' characteristics of the current individual are copied into the cB array. This means that cB stores the characteristic values of the best individual found so far.

    Ultimately, the Revision method scans the current population, finds the individual with the best fitness, and if this individual is better than the current "record", it updates the information about the best found individual (its fitness and the characteristics themselves). This method is necessary to track the progress of the algorithm and find the global optimum.

    //————————————————————————————————————————————————————————————————————
    //--- Update the best and worst solutions
    void C_AO_DA_duelist::Revision ()
    {
      // Update the global best solution
      for (int i = 0; i < popSize; i++)
      {
        if (a [i].f > fB)
        {
          fB = a [i].f;
          ArrayCopy (cB, a [i].c, 0, 0, WHOLE_ARRAY);
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    


    Test results

    Overall, after the research conducted, the duelist algorithm performs quite well overall.

    DA|Duelist Algorithm|100.0|0.01|0.9|0.1|2.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.9278151663330798
    25 Hilly's; Func runs: 10000; result: 0.5377820196319314
    500 Hilly's; Func runs: 10000; result: 0.27792394907287765
    =============================
    5 Forest's; Func runs: 10000; result: 0.8695700230324329
    25 Forest's; Func runs: 10000; result: 0.47535947112902815
    500 Forest's; Func runs: 10000; result: 0.18193288697223736
    =============================
    5 Megacity's; Func runs: 10000; result: 0.6215384615384616
    25 Megacity's; Func runs: 10000; result: 0.3356923076923076
    500 Megacity's; Func runs: 10000; result: 0.11715384615384725
    =============================
    All score: 4.34477 (48.28%)

    The visualization of the algorithm operation shows a small spread in values for both small and large dimensions, which indicates the good search capabilities of the algorithm.

    Hilly

    DA_duelist on the Hilly test function

    Forest

    DA_duelist on the Forest test function

    Megacity

    DA_duelist on the Megacity test function

    Based on the test results, the Duelist algorithm ranks 42nd overall among the tested optimization algorithms.

    # 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)
    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
    26BSAbird swarm algorithm0.893060.649000.262501.804550.924200.711210.249391.884790.693850.326150.100121.120124.80953.44
    27DEAdolphin_echolocation_algorithm0.759950.675720.341711.777380.895820.642230.239411.777460.615380.440310.151151.206844.76252.91
    28HSharmony search0.865090.687820.325271.878180.999990.680020.095901.775920.620000.422670.054581.097254.75152.79
    29SSGsaplings sowing and growing0.778390.649250.395431.823080.859730.624670.174291.658690.646670.441330.105981.193984.67651.95
    30BCOmbacterial chemotaxis optimization M0.759530.622680.314831.697040.893780.613390.225421.732590.653850.420920.144351.219124.64951.65
    31ABOafrican buffalo optimization0.833370.622470.299641.755480.921700.586180.197231.705110.610000.431540.132251.173784.63451.49
    32(PO)ES(PO) evolution strategies0.790250.626470.429351.846060.876160.609430.195911.681510.590000.379330.113221.082554.61051.22
    33FBAFractal-Based Algorithm0.790000.651340.289651.730990.871580.568230.188771.628580.610770.460620.123981.195374.55550.61
    34TSmtabu search M0.877950.614310.291041.783300.928850.518440.190541.637830.610770.382150.121571.114494.53650.40
    35BSObrain storm optimization0.937360.576160.296881.810410.931310.558660.235371.725340.552310.290770.119140.962224.49849.98
    36WOAmwale optimization algorithm M0.845210.562980.262631.670810.931000.522780.163651.617430.663080.411380.113571.188034.47649.74
    37AEFAartificial electric field algorithm0.877000.617530.252351.746880.927290.726980.180641.834900.666150.116310.095080.877544.45949.55
    38AEOartificial ecosystem-based optimization algorithm0.913800.467130.264701.645630.902230.437050.214001.553270.661540.308000.285631.255174.45449.49
    39CAmcamel algorithm M0.786840.560420.351331.698590.827720.560410.243361.631490.648460.330920.134181.113564.44449.37
    40ACOmant colony optimization M0.881900.661270.303771.846930.858730.586800.150511.596040.596670.373330.024720.994724.43849.31
    41CMAEScovariance_matrix_adaptation_evolution_strategy0.762580.720890.000001.483470.820560.796160.000001.616720.758460.490770.000001.249234.34948.33
    42DA_duelistduelist_algorithm0.927820.537780.277921.743520.869570.475360.181931.526860.621530.335690.117151.074374.34548.28
    43BFO-GAbacterial foraging optimization - ga0.891500.551110.315291.757900.969820.396120.063051.428990.726670.275000.035251.036924.22446.93
    44SOAsimple optimization algorithm0.915200.469760.270891.655850.896750.374010.169841.440600.695380.280310.108521.084224.18146.45
    45ABHAartificial bee hive algorithm0.841310.542270.263041.646630.878580.477790.171811.528180.509230.338770.103970.951974.12745.85
    RWrandom walk0.487540.321590.257811.066940.375540.219440.158770.753750.279690.149170.098470.527342.34826.09


    Summary

    Duelist Algorithm demonstrates respectable results in the field of metaheuristic optimization, ranking among the top 45 population-based algorithms. While it does not claim to be the absolute champion among optimization methods, its key advantage is speed. 

    Adaptive learning strategy – in the context of trading system optimization, this means that unsuccessful parameter configurations quickly adopt successful patterns, which is especially valuable when working with non-stationary financial series. 

    A balance of exploitation and exploration — winners do not rest on their laurels, but continue to seek improvement through innovation. This is critical for trading strategies that must adapt to changing market conditions. 

    The element of controlled randomness — the luck factor — naturally models market uncertainty, where even a good strategy can temporarily perform poorly due to short-term volatility. 

    A hierarchical structure with champions ensures the best solutions are preserved and disseminated, which in trading is equivalent to preserving time-tested strategies while simultaneously seeking new opportunities.

    The Duelist Algorithm is a solid solution that, through the metaphor of martial arts, brings a simple yet effective idea to the world of optimization: learn from the strong, experiment in a position of strength, and pass on knowledge to the next generation.

    tab

    Figure 2. Color gradation 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)

    DA_duelist pros and cons:

    Pros:

    1. Fast.
    2. Low variance across the test functions.

    Cons:

    1. Low convergence accuracy.

    An archive with the latest versions of the algorithm code is attached to the article. 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 in 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_DA_duelist.mq5
    ScriptDA_duelist test stand

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

    Attached files |
    DAvduelistk.zip (262.07 KB)
    Last comments | Go to discussion (2)
    Александр Бельмецов
    Александр Бельмецов | 15 Aug 2025 at 04:00
    MetaQuotes:

    An article entitled ‘The Duelist Algorithm’ has been published:

    Author: Andrey Dik

    Duelists fighting in the ring. The market isn’t a ring, but a landscape. One moment you’re up to your waist in a swamp. On the ice, the figure skater will win, but in the swamp... And in the end, what sort of warrior will emerge?
    lynxntech
    lynxntech | 15 Aug 2025 at 04:21
    Александр Бельмецов #:
    Foil fencers fighting in the ring. The market isn’t a ring, but a terrain. One moment you’re up to your waist in a swamp. On the ice, the figure skater will win, but in the swamp… And in the end, what sort of warrior will emerge?

    You can look it up for yourselves online; the author of this thread didn’t make it up.

    Automatic Session Volume Profile Builder in MQL5: Rendering POC and Value Area Without Third-Party Tools Automatic Session Volume Profile Builder in MQL5: Rendering POC and Value Area Without Third-Party Tools
    Implement a session-focused volume profile in MQL5: acquire ticks with CopyTicksRange(), bin prices, and compute POC, VAH, and VAL by the 70% approach. The indicator renders directly on the chart as native objects, supports fixed-width scaling for consistent geometry across timeframes, and refreshes on each new session. This provides objective reference levels without external dependencies.
    Code, Tears, and Algo Forge Code, Tears, and Algo Forge
    This article discusses the transition to MQL5 Algo Forge as a modern and convenient format for publishing program code and article attachments. Using repositories instead of traditional ZIP archives and source code allows you to keep projects up-to-date, make edits quickly, and professionally interact with your readers. Recommendations are provided for quickly migrating developments to the cloud environment via the MetaEditor interface.
    MQL5 Wizard Techniques you should know (Part 100): Sliding Window Median and Bidirectional LSTM for a Custom Trailing Stop MQL5 Wizard Techniques you should know (Part 100): Sliding Window Median and Bidirectional LSTM for a Custom Trailing Stop
    CTrailingSlidingMedianBiLSTM is a custom MQL5 Wizard trailing module that combines robust median/MAD outlier filtering with a BiLSTM context score in the range [-1, 1]. Four algorithm modes (standard, bands, RSI, adaptive) target noise, mean-reverting bursts and liquidity spikes, reducing premature stop adjustments. This module is intended for side-by-side evaluation with diverse entry signals and money management settings.
    Implementation of the Quantum Reservoir Computing (QRC) circuit Implementation of the Quantum Reservoir Computing (QRC) circuit
    A revolutionary approach to machine learning in trading through quantum computing. The article demonstrates a practical implementation of an adaptive QRC system with continuous retraining for predicting market movements in real time.