preview
Алгоритм оптимизации Ройял Флеш —  Royal Flush Optimization (RFO)

Алгоритм оптимизации Ройял Флеш — Royal Flush Optimization (RFO)

MetaTrader 5Торговые системы | 10 февраля 2025, 15:10
460 2
Andrey Dik
Andrey Dik

Содержание

  1. Введение
  2. Реализация алгоритма
  3. Результаты тестов


Введение

Для решения задач оптимизации существует множество подходов, среди которых генетические алгоритмы занимают особое место, благодаря своей способности эффективно исследовать пространство поиска путем имитации процессов естественной эволюции. Классический генетический алгоритм использует бинарное кодирование решений, что требует преобразования вещественных чисел в двоичный формат. Это преобразование вносит не только дополнительную сложность, но и значительно утяжеляет алгоритм. В современном мире, минимизация затрат на расчеты играет решающую роль, и продуктивность метода стремится быть прямо пропорциональной его скорости. Решая эту проблему, мне пришла в голову мысль, каким образом можно сохранить генетические операторы, при этом заменив самые тяжелые вычисления перевода вещественных чисел на более простые и менее энергозатратные операции.

Предлагаемый мной алгоритм "Royal Flush Optimization" (RFO) представляет собой новый подход к решению задач оптимизации, который сохраняет основные преимущества генетических алгоритмов, но использует более прямой способ представления решений. Ключевая идея заключается в разбиении каждой координаты пространства поиска на сектора, подобно тому, как покерная комбинация состоит из отдельных карт определенного ранга. Вместо работы с битовыми строками, алгоритм оперирует рангами карт (номерами секторов), что позволяет естественным образом сохранить топологию пространства поиска.

На мой взгляд, основными преимуществами предлагаемого подхода являются как простота реализации и интуитивная понятность (работа с "картами" более наглядна, чем с битовыми строками), так и отсутствие необходимости в кодировании и декодировании вещественных чисел при сохранении комбинаторных свойств генетического алгоритма. В представленной статье рассмотрим подробно реализацию алгоритма и особенности операторов модификации решений.

Метафора покера не только дает название алгоритму, но и хорошо описывает его суть: подобно тому, как в покере игрок стремится собрать наилучшую комбинацию карт, алгоритм комбинирует сектора разных решений, постепенно формируя оптимальные "руки". Как в покере каждая карта имеет свой ранг и масть, так и в алгоритме каждый сектор имеет свое значение и положение в пространстве поиска. При этом, как и в реальной игре, важна не только ценность отдельных карт, но и их взаимодействие в общей комбинации.

Стоит отметить, что данный подход можно рассматривать, как обобщение идей дискретной оптимизации на случай непрерывных пространств, что открывает интересные перспективы для теоретического анализа и практического применения. Подобно тому, как покер сочетает в себе элементы случайности и стратегии, RFO объединяет случайный поиск с направленной оптимизацией, что делает его эффективным для сложных многомерных задач.


Реализация алгоритма

Принцип работы алгоритма Royal Flush Optimization (RFO) основан на идее представления пространства поиска в виде дискретных секторов, подобно тому, как в покере карты имеют определенные ранги. В классическом генетическом алгоритме значения (оптимизируемые параметры) по всем координатам преобразуются в двоичный код и складываются в последовательность в виде хромосомы, что требует дополнительных вычислительных затрат. В RFO мы отказываемся от этого подхода в пользу более простого и интуитивно понятного представления.

Вместо бинарного кодирования, мы разбиваем каждую координату пространства поиска на сектора, присваивая им значения, аналогичные картам в покере — от валета до туза (J, Q, K, A). Количество рангов (секторов) задается во внешнем параметре алгоритма и может быть любым целочисленным значением. Таким образом, любая точка в пространстве поиска может быть представлена как комбинация карт, где каждая карта соответствует определенному сектору по своей координате. Этот подход не только упрощает вычисления, но и сохраняет комбинаторные свойства генетического алгоритма.

В процессе оптимизации алгоритм работает с "руками" — наборами карт, представляющими различные решения. Операторы кроссовера и мутации применяются непосредственно к "рукам" (наборы карт), где каждая рука — аналог хромосомы, что позволяет исследовать пространство поиска без необходимости преобразования в двоичный код и обратно.

Предложенная ниже иллюстрация (рисунок 1) наглядно демонстрирует этот принцип. На ней показано трехмерное пространство поиска с координатами X, Y и Z, где каждая координата разделена на сектора, соответствующие рангам карт. Справа представлены примеры "рук" - различные комбинации карт, которые алгоритм формирует в процессе поиска оптимального решения.

RFO

Рисунок 1. Алгоритм RFO и разбиение координат на ранги карточной колоды

Переходим к написанию псевдокода алгоритма Royal Flush Optimization (RFO):

  • Инициализация:
    • Задаем количество игроков "покерного стола" (размер популяции)
    • Определяем размер "колоды" (количество секторов на каждое измерение)
    • Устанавливаем вероятность "блефа" (мутации)
    • Создаем начальную "раздачу" — случайно генерируем ранги карт для каждой координаты
    • Преобразуем ранги в реальные значения со случайным смещением внутри сектора
  • Основной цикл игры:
    • Для каждой позиции за столом:
      • Выбираем "оппонента" с помощью квадратичной селекции (более сильные "руки" имеют больший шанс быть выбранными)
      • Копируем текущую "руку" (решение)
      • Выполняем трехточечный обмен картами:
        • Случайно выбираем три точки "разрезания"
        • Сортируем точки разрезания
        • Случайно выбираем начальную точку (0 или 1)
        • Обмениваем карты между текущей рукой и рукой оппонента
        • Возможен "блеф" (вероятность мутации) — случайное изменение ранга карты с заданной вероятностью
      • Преобразуем полученные ранги карт в реальные значения координат
  • Оценка и обновление:
    • Вычисляем ценность каждой "руки" (значение целевой функции)
    • Запоминаем лучшую найденную комбинацию (глобальное лучшее решение)
    • Объединяем текущие руки с предыдущей колодой
    • Сортируем все "руки" по их ценности
    • Лучшие "руки" переходят в следующее поколение
  • Завершение алгоритма по достижению критерия останова
  • Перейдем к написанию кода алгоритма. Напишем структуру "S_RFO_Agent", представляющую объект, содержащий информацию о "руке" в контексте игрового процесса.

    Поля структуры:

    • card [] — массив для хранения вещественного значения карты.
    • f — ценность руки (значение функции пригодности).
    • cardRanks [] — массив целых чисел, представляющий "ранги карт" (номера секторов).

    Метод Init () — инициализирует структуру, принимает один параметр "coords", который указывает количество карт в "руке".

    //——————————————————————————————————————————————————————————————————————————————
    // Структура для представления отдельной "руки"
    struct S_RFO_Agent
    {
        double card [];       // карты
        double f;             // значение функции пригодности ("ценность руки")
        int    cardRanks [];  // номера секторов ("ранги карт")
    
        void Init (int coords)
        {
          ArrayResize (cardRanks, coords);
          ArrayResize (card,      coords);
          f = -DBL_MAX;       // инициализация минимальным значением
        }
    };
    //——————————————————————————————————————————————————————————————————————————————

    Класс "C_AO_RFO" реализует алгоритм и наследует свойства и методы от базового класса "C_AO". Давайте разберем его более подробно.

    Конструктор C_AO_RFO () — устанавливаются значения для переменных класса, инициализация параметров:
    • popSize — размер популяции (покерного стола) устанавливается в 50.
    • deckSize — количество карт в колоде (или секторов) - 1000.
    • dealerBluff — вероятность блефа (мутации) устанавливается на уровне 3% (0.03).
    • Массив params — служит для хранения параметров, изменяется на размер 3 и заполняется значениями, соответствующими "popSize", "deckSize" и "dealerBluff".

    Метод SetParams () метод извлекает значения из массива "params" и присваивает их соответствующим переменным класса.

    Метод Init () предназначен для инициализации класса с переданными параметрами, такими как минимальные и максимальные значения оптимизируемых параметров и их шаг.

    Методы Moving() и Revision() — служат для выполнения операций, связанных с тасованием карт на "руках" и пересмотром лучшей их комбинации.

    Поля класса:
    • deckSize — количество секторов в измерении.
    • dealerBluff — вероятность мутации.
    • deck [], tempDeck [], hand [] — массивы типа "S_RFO_Agent", представляющие основную колоду, временную колоду для сортировки и текущую руку (потомков) соответственно.
    Приватные члены класса:
    • cutPoints — количество точек "разрезания" набора карт на руках, которые используются для комбинации вариантов наборов карт.
    • tempCuts [] и finalCuts [] — массивы для хранения временных и окончательных индексов точек "разрезаний".
    • Методы, используемые в Evolution () — отвечает за выполнение основной эволюции карточных перестановок, а DealCard () - за преобразование сектора в его реальное значение. Метод "ShuffleRanks ()" отвечает за мутацию рангов (случайный выбор среди доступных рангов).
      //——————————————————————————————————————————————————————————————————————————————
      class C_AO_RFO : public C_AO
      {
        public:
        C_AO_RFO ()
        {
          ao_name = "RFO";
          ao_desc = "Royal Flush Optimization";
          ao_link = "https://www.mql5.com/ru/articles/17063";
      
          popSize      = 50;      // размер "покерного стола" (популяции)
          deckSize     = 1000;    // количество "карт" в колоде (секторов)
          dealerBluff  = 0.03;    // вероятность "блефа" (мутации)
      
          ArrayResize (params, 3);
      
          params [0].name = "popSize";      params [0].val = popSize;
          params [1].name = "deckSize";     params [1].val = deckSize;
          params [2].name = "dealerBluff";  params [2].val = dealerBluff;
        }
      
        void SetParams ()
        {
          popSize     = (int)params [0].val;
          deckSize    = (int)params [1].val;
          dealerBluff = params      [2].val;
        }
      
        bool Init (const double &rangeMinP  [],  // минимальные значения
                   const double &rangeMaxP  [],  // максимальные значения
                   const double &rangeStepP [],  // шаг изменения
                   const int     epochsP = 0);   // количество эпох
      
        void Moving   ();
        void Revision ();
      
        //----------------------------------------------------------------------------
        int    deckSize;         // количество секторов в измерении
        double dealerBluff;      // вероятность мутации
      
        S_RFO_Agent deck [];     // основная колода (популяция)
        S_RFO_Agent tempDeck []; // временная колода для сортировки
        S_RFO_Agent hand [];     // текущая рука (потомки)
      
        private: //-------------------------------------------------------------------
        int cutPoints;           // количество точек разрезания
        int tempCuts  [];        // временные индексы точек разрезания
        int finalCuts [];        // финальные индексы с учетом начала и конца
      
        void   Evolution ();     // основной процесс эволюции
        double DealCard (int rank, int suit);  // преобразование сектора в реальное значение
        void   ShuffleRanks (int &ranks []);   // мутация рангов
      };
      //——————————————————————————————————————————————————————————————————————————————

      Метод "Init" предназначен для инициализации объекта класса "C_AO_RFO".

      Метод начинает с вызова функции, которая выполняет стандартную инициализацию заданных параметров, таких как минимальные и максимальные значения, а также шаги изменения параметров. Если эта инициализация не проходит успешно, метод завершает выполнение и возвращает значение "false". После успешной инициализации параметров, метод приступает к подготовке структур данных для хранения информации о "руках" и "колодах". Это включает в себя изменение размеров массивов для хранения "рук" и "колод", чтобы они соответствовали количеству популяции.

      Далее метод инициализирует каждый элемент массива "рук" с помощью специального метода, который настраивает их с учетом указанных координат. Аналогично, массивы "колоды" и "временной колоды" также подготавливаются и инициализируются. Метод устанавливает количество точек разрезания, необходимых для алгоритма кроссовера. В данном случае, устанавливается три точки разрезания (таково лучшее значение, найденное после проведенных экспериментов). Затем осуществляется настройка массивов для хранения временных и окончательных точек разрезания. В конце метод возвращает значение "true", что подтверждает успешное завершение инициализации.

      //——————————————————————————————————————————————————————————————————————————————
      bool C_AO_RFO::Init (const double &rangeMinP  [],
                           const double &rangeMaxP  [],
                           const double &rangeStepP [],
                           const int     epochsP = 0)
      {
        if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
      
        //----------------------------------------------------------------------------
        // Инициализация структур для хранения "рук" и "колод"
        ArrayResize (hand, popSize);
        for (int i = 0; i < popSize; i++) hand [i].Init (coords);
      
        ArrayResize (deck,     popSize * 2);
        ArrayResize (tempDeck, popSize * 2);
        for (int i = 0; i < popSize * 2; i++)
        {
          deck     [i].Init (coords);
          tempDeck [i].Init (coords);
        }
      
        // Инициализация массивов для точек разрезания
        cutPoints = 3;  // три точки разрезания для "трехкарточного" кроссовера
        ArrayResize (tempCuts,  cutPoints);
        ArrayResize (finalCuts, cutPoints + 2);
      
        return true;
      }
      //——————————————————————————————————————————————————————————————————————————————

      Метод "Moving" отвечает за процесс "движения" или обновления состояния популяции в рамках алгоритма RFO.

      Проверка состояния  — метод начинает выполнение с проверки условия, определяющего, была ли закончена начальная инициализация "раздача" карт. Если это не так (revision == false), метод выполняет инициализацию.

      Инициализация начальной раздачи — метод проходит по всем элементам популяции, для каждого элемента популяции (каждой "руки") создается набор карт. Внутренний цикл проходит по каждому необходимому количеству карт и выполняет следующие действия:

      • Случайным образом выбирается ранг карты из колоды.
      • Затем вызывается метод для раздачи карты, основываясь на сгенерированном ранге.
      • Полученная карта корректируется с помощью функции, которая проверяет, находится ли она в заданном диапазоне и производит необходимые изменения, в зависимости от указанных параметров.
      • Наконец, в массив "a" записывается значение полученной карты.

      Обновление состояния — после завершения инициализации "revision" устанавливается в "true", что указывает, начальная раздача завершена, и больше не требуется её повторная инициализация.

      Вызов метода Evolution () — если начальная раздача уже была выполнена, метод переходит к выполнению эволюционного процесса тасования и раздачи карт на "руки".

      //——————————————————————————————————————————————————————————————————————————————
      void C_AO_RFO::Moving ()
      {
        //----------------------------------------------------------------------------
        if (!revision)
        {
          // Инициализация начальной "раздачи"
          for (int i = 0; i < popSize; i++)
          {
            for (int c = 0; c < coords; c++)
            {
              hand [i].cardRanks [c] = u.RNDminusOne (deckSize);
              hand [i].card      [c] = DealCard (hand [i].cardRanks [c], c);
              hand [i].card      [c] = u.SeInDiSp (hand [i].card [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      
              a [i].c [c] = hand [i].card [c];
            }
          }
      
          revision = true;
          return;
        }
      
        //----------------------------------------------------------------------------
        Evolution ();
      }
      //——————————————————————————————————————————————————————————————————————————————

      Метод "Revision" отвечает за поиск наилучшей "комбинации" на "руках", оценку их пригодности и обновление общей колоды.

      Поиск лучшей комбинации:

      • Метод начинает с инициализации переменной "bestHand", которая будет хранить индекс наилучшей руки среди всех членов популяции.
      • Затем выполняется цикл, проходящий по всем элементам популяции (от 0 до popSize). Внутри цикла метод сравнивает значение пригодности каждой руки "a" с текущим наилучшим значением "fB".
      • Если значение пригодности текущей руки больше, чем "fB", происходит обновление с новым наилучшим значением, а переменной "bestHand" присваивается индекс этой "руки".

      Если была найдена наилучшая рука, ее карты копируются в массив "cB", что позволяет сохранить состояние наилучшей комбинации (наилучшее глобальное решение). Метод затем обновляет значения пригодности для каждой руки в массиве "hand", устанавливая их равными соответствующим значениям в массиве "a". Это необходимо для обеспечения актуальности данных о пригодности каждой руки. После обновления значений пригодности, текущие руки из массива "hand" добавляются в общий массив "deck", начиная с позиции "popSize" (то есть, в конец популяции, ее вторую половину).

      В завершение, метод выполняет сортировку массива "deck", используя отдельный временный массив "tempDeck", чтобы упорядочить колоду по ценности комбинаций. Это позволяет использовать повышенную вероятность отбора ценных комбинаций карт при селекции с последующим их комбинированием.

      //——————————————————————————————————————————————————————————————————————————————
      void C_AO_RFO::Revision ()
      {
        // Поиск лучшей "комбинации"
        int bestHand = -1;
      
        for (int i = 0; i < popSize; i++)
        {
          if (a [i].f > fB)
          {
            fB = a [i].f;
            bestHand = i;
          }
        }
      
        if (bestHand != -1) ArrayCopy (cB, a [bestHand].c, 0, 0, WHOLE_ARRAY);
      
        //----------------------------------------------------------------------------
        // Обновление значений пригодности
        for (int i = 0; i < popSize; i++)
        {
          hand [i].f = a [i].f;
        }
      
        // Добавление текущих рук в общую колоду
        for (int i = 0; i < popSize; i++)
        {
          deck [popSize + i] = hand [i];
        }
      
        // Сортировка колоды по ценности комбинаций
        u.Sorting (deck, tempDeck, popSize * 2);
      }
      //——————————————————————————————————————————————————————————————————————————————

      Метод "Evolution" отвечает за основную логику алгоритма, в котором происходит обмен картами между "руками" игроков за столом, "блеф" и обновление вещественных значений карт.

      Метод начинается с цикла, который проходит по всем элементам популяции. Для каждого элемента выполняются следующие действия:

      Выбор оппонента:

      • Для выбора оппонента используется генерация случайного числа, которое затем поднимается в квадрат для усиления селекции (вероятность выбора оппонента пересекается с его рейтингом). Это позволяет более вероятно выбирать лучшее сочетание рук.
      • Случайное число масштабируется с использованием функции "u.Scale", чтобы получить индекс оппонента.

      Текущая рука (из массива "deck") копируется в массив "hand". Метод генерирует случайные точки "разрезания" набора карт на руках. Эти точки определяют, какие карты будут обмениваться между двумя руками. Точки разрезания сортируются, и к ним добавляются границы; первая граница устанавливается на "0", а последняя — на "coords - 1". Метод выбирает случайную начальную точку для начала обмена картами, используя "u.RNDbool ()". После выполнения обмена картами, производится шанс на "блеф".

      Преобразование рангов в реальные значения:
      • В заключительном цикле происходит преобразование рангов карт в их значения, используя метод "DealCard", и осуществляется проверка на соответствие установленным границам.
      • После этого обновляются значения в массиве "a", который, содержит финальные карты.

      //——————————————————————————————————————————————————————————————————————————————
      void C_AO_RFO::Evolution ()
      {
        // Для каждой позиции за столом
        for (int i = 0; i < popSize; i++)
        {
          // Выбор оппонента с учетом его рейтинга (квадрат вероятности для усиления селекции)
          double rnd = u.RNDprobab ();
          rnd *= rnd;
          int opponent = (int)u.Scale (rnd, 0.0, 1.0, 0, popSize - 1);
      
          // Копирование текущей руки
          hand [i] = deck [i];
      
          // Определение точек разрезания для обмена картами
          for (int j = 0; j < cutPoints; j++)
          {
            tempCuts [j] = u.RNDminusOne (coords);
          }
      
          // Сортировка точек разрезания и добавление границ
          ArraySort (tempCuts);
          ArrayCopy (finalCuts, tempCuts, 1, 0, WHOLE_ARRAY);
          finalCuts [0] = 0;
          finalCuts [cutPoints + 1] = coords - 1;
      
          // Случайный выбор начальной точки для обмена
          int startPoint = u.RNDbool ();
      
          // Обмен картами между руками
          for (int j = startPoint; j < cutPoints + 2; j += 2)
          {
            if (j < cutPoints + 1)
            {
              for (int len = finalCuts [j]; len < finalCuts [j + 1]; len++) hand [i].cardRanks [len] = deck [opponent].cardRanks [len];
            }
          }
      
          // Возможность "блефа" (мутации)
          ShuffleRanks (hand [i].cardRanks);
      
          // Преобразование рангов в реальные значения
          for (int c = 0; c < coords; c++)
          {
            hand [i].card [c] = DealCard (hand [i].cardRanks [c], c);
            hand [i].card [c] = u.SeInDiSp (hand [i].card [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      
            a [i].c [c] = hand [i].card [c];
          }
        }
      }
      //——————————————————————————————————————————————————————————————————————————————

      Метод "DealCard" является ключевым элементом алгоритма Royal Flush Optimization, преобразующим дискретные сектора пространства поиска в непрерывные значения координат. На вход метод принимает два параметра: "rank" — ранг карты и "suit" — индекс координаты (масть).

      Процесс преобразования состоит из двух этапов. Сначала вычисляется размер одного сектора (suitRange), путем деления всего диапазона поиска на количество секторов. Затем генерируется конкретное значение внутри выбранного сектора. Случайное смещение "u.RNDprobab ()" обеспечивает равномерное исследование пространства внутри каждого сектора, а "rank" определяет базовое положение в пространстве поиска.

      Такой подход позволяет сочетать дискретное представление решений через сектора с непрерывным пространством поиска, обеспечивая баланс между глобальным и локальным поиском.

      //——————————————————————————————————————————————————————————————————————————————
      double C_AO_RFO::DealCard (int rank, int suit)
      {
        // Преобразование ранга карты в реальное значение с случайным смещением внутри сектора
        double suitRange = (rangeMax [suit] - rangeMin [suit]) / deckSize;
        return rangeMin [suit] + (u.RNDprobab () + rank) * suitRange;
      }
      //——————————————————————————————————————————————————————————————————————————————

      Метод "ShuffleRanks" реализует механизм мутации в алгоритме Royal Flush Optimization, выступая в роли "блефа" при работе с рангами карт. Принимая на вход массив рангов по ссылке, метод проходит по каждой координате и, с вероятностью "dealerBluff", заменяет текущий ранг на случайное значение из диапазона допустимых рангов колоды. Этот процесс можно сравнить с ситуацией в покере, когда игрок неожиданно меняет карту в своей руке, внося элемент непредсказуемости в игру. Такой механизм мутации призван помогать алгоритму избегать застревания в локальных оптимумах и поддерживает разнообразие возможных решений в процессе оптимизации.

      //——————————————————————————————————————————————————————————————————————————————
      void C_AO_RFO::ShuffleRanks (int &ranks [])
      {
        // Перетасовка рангов (мутация)
        for (int i = 0; i < coords; i++)
        {
          if (u.RNDprobab () < dealerBluff) ranks [i] = (int)MathRand () % deckSize;
        }
      }
      //——————————————————————————————————————————————————————————————————————————————


      Результаты тестов

      Распечатка результатов тестирования алгоритма RFO:

      RFO|Royal Flush Optimization|50.0|1000.0|0.03|
      =============================
      5 Hilly's; Func runs: 10000; result: 0.8336125672709654
      25 Hilly's; Func runs: 10000; result: 0.7374210861383783
      500 Hilly's; Func runs: 10000; result: 0.34629436610445113
      =============================
      5 Forest's; Func runs: 10000; result: 0.8942431024645086
      25 Forest's; Func runs: 10000; result: 0.7382367793268382
      500 Forest's; Func runs: 10000; result: 0.24097956383750824
      =============================
      5 Megacity's; Func runs: 10000; result: 0.6315384615384616
      25 Megacity's; Func runs: 10000; result: 0.5029230769230771
      500 Megacity's; Func runs: 10000; result: 0.16420769230769366
      =============================
      All score: 5.08946 (56.55%)

      Итоговая оценка в 56.55% — очень достойный результат. На визуализации алгоритм не демонстрирует какого-то специфичного поведения, выглядит как хаотичное блуждание отдельных точек.

      Hilly

        RFO на тестовой функции Hilly

      Forest

      RFO на тестовой функции Forest

      Megacity

      RFO на тестовой функции Megacity

      По результатам тестирования алгоритм оптимизации RFO занимает 15-е место, пополнив список самых сильнейших известных алгоритмов.

      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 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
      9 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
      10 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
      11 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
      12 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
      13 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
      14 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
      15 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
      16 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
      17 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
      18 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
      19 CRO chemical reaction optimisation 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
      20 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
      21 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
      22 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
      23 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
      24 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
      25 (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
      26 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
      27 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
      28 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
      29 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
      30 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
      31 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
      32 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
      33 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
      34 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
      35 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
      36 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
      37 ATAm artificial tribe algorithm M 0,71771 0,55304 0,25235 1,52310 0,82491 0,55904 0,20473 1,58867 0,44000 0,18615 0,09411 0,72026 3,832 42,58
      38 ASHA artificial showering algorithm 0,89686 0,40433 0,25617 1,55737 0,80360 0,35526 0,19160 1,35046 0,47692 0,18123 0,09774 0,75589 3,664 40,71
      39 ASBO adaptive social behavior optimization 0,76331 0,49253 0,32619 1,58202 0,79546 0,40035 0,26097 1,45677 0,26462 0,17169 0,18200 0,61831 3,657 40,63
      40 MEC mind evolutionary computation 0,69533 0,53376 0,32661 1,55569 0,72464 0,33036 0,07198 1,12698 0,52500 0,22000 0,04198 0,78698 3,470 38,55
      41 IWO invasive weed optimization 0,72679 0,52256 0,33123 1,58058 0,70756 0,33955 0,07484 1,12196 0,42333 0,23067 0,04617 0,70017 3,403 37,81
      42 Micro-AIS micro artificial immune system 0,79547 0,51922 0,30861 1,62330 0,72956 0,36879 0,09398 1,19233 0,37667 0,15867 0,02802 0,56335 3,379 37,54
      43 COAm cuckoo optimization algorithm M 0,75820 0,48652 0,31369 1,55841 0,74054 0,28051 0,05599 1,07704 0,50500 0,17467 0,03380 0,71347 3,349 37,21
      44 SDOm spiral dynamics optimization M 0,74601 0,44623 0,29687 1,48912 0,70204 0,34678 0,10944 1,15826 0,42833 0,16767 0,03663 0,63263 3,280 36,44
      45 NMm Nelder-Mead method M 0,73807 0,50598 0,31342 1,55747 0,63674 0,28302 0,08221 1,00197 0,44667 0,18667 0,04028 0,67362 3,233 35,92
      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


      Выводы

      В процессе исследования и разработки новых методов оптимизации мы часто сталкиваемся с необходимостью поиска баланса между эффективностью и сложностью реализации. Работа над алгоритмом Royal Flush Optimization (RFO) привела к интересным результатам, которые заставляют задуматься о природе оптимизационных процессов и путях их совершенствования.

      Наблюдая за работой алгоритма, достигающего 57% от максимально возможной теоретически производительности, мы видим интересный феномен: иногда упрощение может быть более ценным, чем усложнение. RFO демонстрирует, что отказ от сложного бинарного кодирования в пользу более прямолинейного секторного подхода может привести к существенному ускорению работы алгоритма, при сохранении достаточно высокого качества решений. Это напоминает ситуацию в покере, где иногда более простая, но быстрая стратегия может оказаться эффективнее сложной и требующей длительных расчетов.

      Размышляя о месте RFO в семействе оптимизационных алгоритмов, можно провести аналогию с эволюцией транспортных средств. Подобно тому, как наряду с мощными спортивными автомобилями существует потребность в экономичных городских моделях, так и в мире алгоритмов оптимизации есть место для методов, ориентированных на разные приоритеты. RFO можно рассматривать как "экономичный" вариант генетического алгоритма, предлагающий разумный компромисс между результативностью и ресурсоэффективностью.

      В заключение стоит отметить, что разработка RFO открывает интересные перспективы для дальнейших исследований. Возможно, это только первый шаг в развитии целого семейства алгоритмов, основанных на секторном подходе к оптимизации. Простота и элегантность метода, сочетающаяся с его практической эффективностью, может послужить источником вдохновения для создания новых алгоритмов, балансирующих между производительностью и вычислительной эффективностью.

      Стоит отметить, что разбивка на сектора происходит виртуально, без выделения на это памяти в виде массивов. Данная основа RFO является отличной отправной точкой для дальнейшей разработки усовершенствованных версий покерного алгоритма.

      Tab

      Рисунок 2. Цветовая градация алгоритмов по соответствующим тестам

      Chart

      Рисунок 3. Гистограмма результатов тестирования алгоритмов (по шкале от 0 до 100, чем больше, тем лучше, где 100 - максимально возможный теоретический результат, в архиве скрипт для расчета рейтинговой таблицы)


      Плюсы и минусы алгоритма RFO:

      Плюсы:

      1. Мало внешних параметров, только два, не считая размер популяции.
      2. Простая реализация.
      3. Быстрый.
      4. Сбалансированные, хорошие показатели на задачах различных размерностей.

      Минусы:

      1. Средняя точность сходимости.

      К статье прикреплён архив с актуальными версиями кодов алгоритмов. Автор статьи не несёт ответственности за абсолютную точность в описании канонических алгоритмов, во многие из них были внесены изменения для улучшения поисковых возможностей. Выводы и суждения, представленные в статьях, основываются на результатах проведённых экспериментов.

      Программы, используемые в статье

      # Имя Тип Описание
      1 #C_AO.mqh
      Включаемый файл
      Родительский класс популяционных алгоритмов оптимизации
      2 #C_AO_enum.mqh
      Включаемый файл
      Перечисление популяционных алгоритмов оптимизации
      3 TestFunctions.mqh
      Включаемый файл
      Библиотека тестовых функций
      4
      TestStandFunctions.mqh
      Включаемый файл
      Библиотека функций тестового стенда
      5
      Utilities.mqh
      Включаемый файл
      Библиотека вспомогательных функций
      6
      CalculationTestResults.mqh
      Включаемый файл
      Скрипт для расчета результатов в сравнительную таблицу
      7
      Testing AOs.mq5
      Скрипт Единый испытательный стенд для всех популяционных алгоритмов оптимизации
      8
      Simple use of population optimization algorithms.mq5
      Скрипт
      Простой пример использования популяционных алгоритмов оптимизации без визуализации
      9
      Test_AO_RFO.mq5
      Скрипт Испытательный стенд для RFO
      Прикрепленные файлы |
      RFO.ZIP (160.26 KB)
      Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
      blef
      blef | 12 февр. 2025 в 03:57

      А как конкретно( в плане идей)
      можно адаптировать расчеты
       в ЭА именно для ВР? Посмотрел
      на вскидку и на сайте mql не нашел конкретики.
      Andrey Dik
      Andrey Dik | 12 февр. 2025 в 05:08
      blef #:

      А как конкретно( в плане идей)
      можно адаптировать расчеты
       в ЭА именно для ВР? Посмотрел
      на вскидку и на сайте mql не нашел конкретики.

      Во всех случаях, когда необходимо найти лучшее решение среди множества возможных. Например, советники с самооптимизацией.

      От начального до среднего уровня: Оператор FOR От начального до среднего уровня: Оператор FOR
      В этой статье мы рассмотрим самые основные понятия оператора FOR. Всё, что будет здесь показано, нужно хорошо понять и усвоить. В отличие от других операторов, о которых мы говорили ранее, оператор FOR имеет некоторые особенности, которые быстро делают его очень сложным. Так что не позволяйте подобным материалам накапливаться. Приступайте к изучению и практике как можно скорее.
      Нейросети в трейдинге: Мультизадачное обучение на основе модели ResNeXt (Окончание) Нейросети в трейдинге: Мультизадачное обучение на основе модели ResNeXt (Окончание)
      Продолжаем изучение фреймворка мультизадачного обучения на основе ResNeXt, который отличается модульностью, высокой вычислительной эффективностью и способностью выявлять устойчивые паттерны в данных. Использование единого энкодера и специализированных "голов" снижает риск переобучения модели и повышает качество прогнозов.
      MQL5-советник, интегрированный в Telegram (Часть 2): Отправка сигналов из MQL5 в Telegram MQL5-советник, интегрированный в Telegram (Часть 2): Отправка сигналов из MQL5 в Telegram
      В этой статье мы создадим MQL5-советник, интегрированный с Telegram, который отправляет в мессенджер сигналы пересечения скользящих средних. Мы подробно опишем процесс генерации торговых сигналов на основе пересечений скользящих средних, реализуем необходимый код на языке MQL5 и обеспечим бесперебойную работу интеграции. В результате мы получим систему, которая отправляет торговые оповещения в реальном времени непосредственно в групповой чат Telegram.
      MQL5-советник, интегрированный в Telegram (Часть 1): Отправка сообщений из MQL5 в Telegram MQL5-советник, интегрированный в Telegram (Часть 1): Отправка сообщений из MQL5 в Telegram
      В этой статье мы создадим советник на языке MQL5, отправляющий сообщения в Telegram с помощью бота. Мы настроим необходимые параметры, включая API-токен бота и идентификатор чата, а затем выполним HTTP-запрос POST для доставки сообщений. Затем мы обработаем ответ, чтобы обеспечить успешную доставку, и устраним возможные ошибки.