preview
Алгоритм голубых обезьян — Blue Monkey (BM) Algorithm

Алгоритм голубых обезьян — Blue Monkey (BM) Algorithm

MetaTrader 5Трейдинг |
426 0
Andrey Dik
Andrey Dik

Содержание

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


Введение

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

Алгоритм был разработан M. Mahmood и B. Al-Khateeb и опубликован в 2019 г. в Periodicals of Engineering and Natural Sciences (PEN) 7(3):1054-1066 DOI:10.21533/pen.v7i3.621.

Эта сложная, но гармоничная экосистема нашла своё отражение в математической абстракции алгоритма Blue Monkey:

  • Группы как Команды. Популяция агентов разделяется на независимые команды, каждая из которых имеет своего лидера, подобно доминантному самцу.
  • Лидер – Воплощение Совершенства. Лучшая особь в каждой группе, обладающая наивысшей "приспособленностью" (fitness), олицетворяет собой доминантного самца — образец для подражания.
  • Детёныши — Новое Поколение. Молодые особи, "детёныши", представляют собой новый виток развития, учащийся и готовый заменить своих старших, привнося свежие идеи и решения.
  • Миграция — Обновление Системы. Процесс миграции воплощается в том, что лучшие "детёныши" могут заменить менее приспособленных "взрослых", обеспечивая постоянное обновление и повышение эффективности.
  • Социальное Обучение — Путь к Лидеру. Движение "детёнышей" к лидеру группы, управляемое математическими формулами, имитирует процесс социального обучения, когда молодые учатся у опытных.


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

Представьте, что вы управляете исследовательской экспедицией в джунглях, которая ищет затерянный город. У вас есть 50 опытных исследователей (взрослые обезьяны) и 15 стажёров-студентов (детёныши — 30% от взрослых).

Организация работы:

  • опытных исследователей делим на 5 команд по 10 человек;
  • каждая команда исследует свой участок джунглей независимо;
  • 5 команд = 5 разных зон поиска: команда А пойдет на север, команда Б отправится на юг, команда В ищет на востоке, команда Г исследует на западе, команда Д остается в центре. 

Если затерянный город на севере, команда А найдёт его быстрее, а остальные постепенно подтянутся. Стажёры работают все вместе как учебная группа. Главный механизм 5 команд = 5 разных зон поиска. "Следуй за успешным". В каждой команде определяется лидер — тот, кто нашёл самое правильное направление, остальные корректируют свой маршрут в сторону лидера, но не идут прямо к нему! Сохраняют 70% своего направления + 30% поворачивают к лидеру. Не бросаем сразу свой маршрут (вдруг там тоже что-то есть), но постепенно смещаемся к успешным местам.

Обучение молодых: стажёры делают то же самое, но все следуют за одним лучшим стажёром. Находим худшего в каждой из команд и сравниваем с лучшими стажёрами, и если стажёр лучше — он получает постоянную работу, а неудачник увольняется. Стажёры приносят новые идеи — возможно, опытный исследователь застрял в плохом районе, а молодой случайно нашёл золотую жилу.

blue_monkey

Рисунок 1. Схема работы алгоритма BM

На схеме отображена работа алгоритма BM с разделением логики на два независимых направления для взрослых особей и детенышей с основными формулами. Можем перейти к составлению псевдокода алгоритма BM.

Инициализация:

  1. Создать популяцию взрослых обезьян (размер N)
    • Каждая обезьяна получает случайную позицию в пространстве поиска
    • Инициализировать скорость движения нулем
    • Присвоить начальный вес от 4 до 6
  2. Создать популяцию детёнышей (30% от размера взрослых)
    • Каждый детёныш получает случайную позицию
    • Инициализировать скорость движения нулем
    • Присвоить начальный вес от 4 до 6
  3. Распределить взрослых по группам
    • Разделить популяцию на T групп равномерно
    • Обезьяна с индексом i попадает в группу (i модуль T)
  4. Вычислить начальный фитнес
    • Оценить качество позиции каждого взрослого
    • Оценить качество позиции каждого детёныша

Основной цикл (пока не достигнут критерий остановки):

  1. Обновить веса на основе фитнеса
    • Найти минимальный и максимальный фитнес среди взрослых
    • Нормализовать веса: вес = 4 + 2 × (фитнес - мин)/(макс - мин)
    • Повторить для детёнышей
  2. Заменить худших на лучших (начиная со второй итерации)
    • Для каждой группы взрослых:
      • Найти обезьяну с худшим фитнесом в группе
      • Взять следующего лучшего неиспользованного детёныша
      • Если фитнес детёныша лучше фитнеса взрослого:
        • Заменить взрослого на детёныша
        • Создать нового детёныша на освободившееся место
  3. Обновить позиции взрослых обезьян
    • Для каждой обезьяны в группе:
      • Найти лидера группы (обезьяну с лучшим фитнесом)
      • Обновить скорость: новая скорость = 0.7 × старая скорость + (вес лидера - личный вес) × случайное × (позиция лидера - личная позиция)
      • Обновить позицию: новая позиция = старая позиция + скорость × случайное
      • Ограничить позицию границами пространства поиска
  4. Обновить позиции детёнышей
    • Найти лучшего детёныша среди всех
    • Для каждого другого детёныша:
      • Обновить скорость: новая скорость = 0.7 × старая скорость + (вес лучшего - личный вес) × случайное × (позиция лучшего - личная позиция)
      • Обновить позицию: новая позиция = старая позиция + скорость × случайное
      • Ограничить позицию границами
  5. Вычислить новый фитнес
    • Оценить качество новых позиций всех обезьян
    • Оценить качество новых позиций всех детёнышей
  6. Обновить глобальное лучшее решение
    • Если найдено решение лучше текущего рекорда:
      • Сохранить его как новый рекорд
    • Увеличить счетчик итераций
  7. Проверить критерий остановки
    • Если достигнуто максимальное число итераций ИЛИ
    • Если достигнута требуемая точность:
      • Выйти из цикла
    • Иначе: вернуться к шагу 5

Завершение:

  1. Вернуть результат
    • Вернуть лучшую найденную позицию
    • Вернуть значение фитнеса в этой позиции

Теперь воплощаем псевдокод в реализацию. Напишем класс "C_AO_BM". Он является наследником класса "C_AO", представляющим собой базовый класс. Класс "C_AO_BM" наследует все публичные и защищенные поля класса "C_AO". 

Публичные поля:

Конструктор "C_AO_BM" инициализирует базовые свойства алгоритма и устанавливает начальные параметры алгоритма: "popSize" (размер популяции), "numGroups" (количество групп), "childrenRatio" (доля детёнышей). Настраивает массив "params", который используется для хранения настраиваемых параметров алгоритма. Для каждого параметра указывается его имя и текущее значение. Устанавливает минимальное "minW" и максимальное "maxW" значения для весов, используемых в алгоритме.
  • SetParams() — метод для установки параметров алгоритма из массива "params". Он считывает значения "val" из "params" и преобразует их в соответствующие типы данных (int или double), присваивая их членам класса.
  • Init() — метод инициализации подготавливает алгоритм к работе, используя входные диапазоны (rangeMinP, rangeMaxP, rangeStepP) и количество эпох (epochsP).
  • Moving () — метод реализует основную логику перемещения или обновления особей в популяции согласно алгоритму.
  • Revision () — метод используется для пересмотра или оценки текущего состояния популяции, возможного отбора и обновления.
Публичные переменные-поля:
  • numGroups — количество групп, на которые разбивается популяция.
  • childrenRatio — процент популяции, который будет представлен "детёнышами" (новыми особями).
Приватные поля:
  • numChildren — количество "детёнышей" в популяции, рассчитываемое на основе "popSize" и "childrenRatio".
  • minW, maxW — минимальные и максимальные значения для весов.
  • Массивы для взрослых обезьян:
    • monkeyRate — хранит "скорость" для каждой особи в популяции. Размер массива зависит от "popSize" и количества измерений (координат) в пространстве поиска.
    • monkeyWeight — хранит "веса" (W) для каждой взрослой особи.
    • groupId — массив, указывающий, к какой группе принадлежит каждая взрослая особь.
  • Массивы для детёнышей хранят:
    • childPos — позиции "детёнышей" в пространстве поиска.
    • childRate — "скорость" для "детёнышей".
    • childWeight — "веса" для "детёнышей".
    • childFitness — значение приспособленности (fitness) для "детёнышей".
  • Приватные методы:
    • SwapWorstWithBest () — реализует шаг 8 алгоритма, который предполагает замену худшей особи в группе на лучшую.
    • UpdatePositions () — реализует шаги 9-10 алгоритма, отвечающие за обновление позиций особей.
    • UpdateWeights () — реализует шаг 2 алгоритма и общее обновление весов.
    • FindGroupBest () — находит лучшую особь в указанной группе.
    • FindGroupWorst () — находит худшую особь в указанной группе.

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

//————————————————————————————————————————————————————————————————————
class C_AO_BM : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_BM () { }
  C_AO_BM ()
  {
    ao_name = "BM";
    ao_desc = "Blue Monkey Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/19757";

    popSize        = 50;    // размер популяции
    numGroups      = 5;     // количество групп T
    childrenRatio  = 0.3;   // процент детёнышей от популяции

    ArrayResize (params, 3);
    params [0].name = "popSize";       params [0].val = popSize;
    params [1].name = "numGroups";     params [1].val = numGroups;
    params [2].name = "childrenRatio"; params [2].val = childrenRatio;

    minW           = 4.0;
    maxW           = 6.0;
  }

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

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  int    numGroups;      // количество групп T
  double childrenRatio;  // процент детёнышей

  private: //---------------------------------------------------------
  int    numChildren;         // количество детёнышей
  double minW;
  double maxW;

  // Массивы для взрослых обезьян
  double monkeyRate   [];     // Rate - скорость изменения [popSize * coords]
  double monkeyWeight [];     // W - веса обезьян
  int    groupId      [];     // принадлежность к группе

  // Массивы для детёнышей
  double childPos     [];      // позиции детёнышей [numChildren * coords]
  double childRate    [];      // Rate для детёнышей
  double childWeight  [];      // W для детёнышей
  double childFitness [];      // фитнес детёнышей

  void SwapWorstWithBest ();   // Шаг 8
  void UpdatePositions   ();   // Шаги 9-10
  void UpdateWeights     ();   // Шаг 2 и обновление
  int  FindGroupBest     (int groupId);
  int  FindGroupWorst    (int groupId);
};
//————————————————————————————————————————————————————————————————————

Функция "Init" выполняет следующие действия:

Стандартная инициализация. Сначала вызывается базовая функция инициализации "StandardInit" с переданными параметрами диапазонов (rangeMinP, rangeMaxP, rangeStepP). Если эта базовая инициализация завершается неудачно, функция "Init" также завершается, возвращая "false".

Корректировка параметров:

  • Проверяется количество групп (numGroups).
  • Рассчитывается количество "детёнышей" (numChildren) на основе размера популяции и доли детёнышей (childrenRatio). 
Изменение размеров массивов:
  • Изменяется размер массивов, предназначенных для хранения данных "взрослых" особей. Это включает "monkeyRate" (где скорость хранится для каждого измерения), "monkeyWeight" (вес для каждой особи) и "groupId" (принадлежность особи к группе).
  • Изменяется размер массивов для "детёнышей", включая их позиции (childPos), скорости (childRate), веса (childWeight) и значения приспособленности (childFitness).

Инициализация скоростей. Массивы "monkeyRate" и "childRate" (которые используются для определения направления движения) инициализируются нулевыми значениями.

Инициализация весов: Веса (monkeyWeight и childWeight) для "взрослых" и "детёнышей" инициализируются случайными значениями в диапазоне от "minW" до "maxW", фактические веса будут пересчитаны после первого вычисления приспособленности.

Распределение по группам:

  • Взрослые особи распределяются по группам (groupId) по модулю. То есть, первая особь попадает в группу 0, вторая в группу 1, ..., (numGroups) в группу numGroups -1, (numGroups + 1) снова в группу 0, и так далее.
  • Все детёныши будут находиться изначально в одной команде.

Инициализация приспособленности детёнышей: Массив "childFitness" инициализируется минимально возможным значением. Это делается для того, чтобы любое реальное рассчитанное значение приспособленности было больше этого начального значения.

Функция возвращает "true", если инициализация прошла успешно. Таким образом, функция "Init" выполняет всю подготовительную работу: устанавливает необходимые размеры структур данных, задает начальные значения для скорости и весов, распределяет особей по группам и подготавливает их к первому циклу вычислений.

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

  //------------------------------------------------------------------
  // Корректировка параметров
  if (numGroups < 1) numGroups = 1;
  if (numGroups > popSize) numGroups = popSize;

  numChildren = (int)(popSize * childrenRatio);
  if (numChildren < 1) numChildren = 1;

  // Инициализация массивов для взрослых
  ArrayResize (monkeyRate, popSize * coords);
  ArrayResize (monkeyWeight, popSize);
  ArrayResize (groupId, popSize);

  // Инициализация массивов для детёнышей
  ArrayResize (childPos, numChildren * coords);
  ArrayResize (childRate, numChildren * coords);
  ArrayResize (childWeight, numChildren);
  ArrayResize (childFitness, numChildren);

  // Шаг 2: Initialize Power Rate Rate and Weight W
  // Rate ∈ [0, 1], W ∈ [4,6]
  ArrayInitialize (monkeyRate, 0.0);
  ArrayInitialize (childRate, 0.0);

  // Веса будут обновлены после первого вычисления фитнеса
  for (int i = 0; i < popSize; i++)
  {
    monkeyWeight [i] = u.RNDfromCI (minW, maxW);
  }

  for (int i = 0; i < numChildren; i++)
  {
    childWeight [i] = u.RNDfromCI (minW, maxW);
  }

  // Шаг 3: Распределение по группам
  // Взрослые распределяются по T группам
  for (int i = 0; i < popSize; i++)
  {
    groupId [i] = i % numGroups;
  }
  // "while all children in one team" - все детёныши в одной команде изначально

  ArrayInitialize (childFitness, -DBL_MAX);

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

Метод "Moving" выполняет следующие действия: сначала проверяется флаг "revision" и если "revision" равно "false" (первый запуск):

Инициализация популяции взрослых (Шаг 1). Для каждой взрослой особи "i" от (0 до popSize  - 1) и для каждого измерения "j" от (0 до coords  - 1) позиция (a [i].c [j]) инициализируется случайным числом в заданном диапазоне (rangeMin [j], rangeMax [j]). Значение позиции затем корректируется с помощью функции "u.SeInDiSp", которая обеспечивает нахождение значения в заданных пределах и с учетом шага (rangeStep [j]).

Инициализация популяции детёнышей (Шаг 1). Для каждого детёныша "i" от (0 до numChildren  - 1) и для каждого измерения "j" от (0 до  coords  - 1) позиция (childPos [i * coords + j]) инициализируется случайным числом в заданном диапазоне (rangeMin, rangeMax). Значение позиции также корректируется функцией "u.SeInDiSp".

Флаг "revision" устанавливается в "true", чтобы при следующих вызовах метода "Moving" выполнялась другая часть логики. Метод завершает выполнение. Если "revision" равно "true" (последующие запуски) происходит обновление позиций, вызывается метод "UpdatePositions ()". Этот метод отвечает за перемещение особей в соответствии с логикой алгоритма "синей обезьяны".

Таким образом, метод "Moving" служит двум основным целям:

  • При первом вызове он полностью инициализирует позиции всех взрослой особей и детёнышей в пределах заданных диапазонов.
  • При последующих вызовах он делегирует основную работу по перемещению особей согласно метода "UpdatePositions ()", который реализует ядро движения алгоритма.
//————————————————————————————————————————————————————————————————————
void C_AO_BM::Moving ()
{
  if (!revision)
  {
    // Шаг 1: Инициализация популяции взрослых
    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]);
      }
    }

    // Шаг 1: Инициализация популяции детёнышей
    for (int i = 0; i < numChildren; i++)
    {
      for (int j = 0; j < coords; j++)
      {
        childPos [i * coords + j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
        childPos [i * coords + j] = u.SeInDiSp (childPos [i * coords + j], rangeMin [j], rangeMax [j], rangeStep [j]);
      }
    }

    revision = true;
    return;
  }

  //------------------------------------------------------------------
  UpdatePositions ();
}
//————————————————————————————————————————————————————————————————————

Метод "Revision" выполняет следующие действия:

Обновление весов. Сначала вызывается метод "UpdateWeights ()". Это означает, что веса каждого индивида пересчитываются на основе их текущих значений приспособленности.
Обмен худшего с лучшим. Далее вызывается метод "SwapWorstWithBest ()". Этот метод находит индивида с наихудшим значением приспособленности и замещает его с индивидом, имеющим наилучшее значение приспособленности. 

    Обновление лучшей особи.Для взрослых особей происходит итерация по всем взрослым особям "i" (от 0 до popSize  - 1) и если текущее значение приспособленности взрослой особи (a [i].f) больше, чем лучшее значение приспособленности, которое эта особь когда-либо достигала (a [i].fB), то значение приспособленности особи обновляется. Позиция лучшего достижения особи (a [i].cB) копируется из текущей позиции особи (a [i].c).

    Аналогично, если текущее значение приспособленности взрослой особи (a [i].f) больше, чем общее лучшее значение приспособленности во всей популяции (fB), то общее лучшее значение приспособленности (fB) обновляется. Позиция общего лучшего решения (cB) копируется из текущей позиции особи (a [i].c).

    Для детёнышей. Происходит итерация по всем детёнышам "i" (от 0 до numChildren  - 1). Если приспособленность детёныша (childFitness [i]) больше, чем общее лучшее значение приспособленности (fB), то общее лучшее значение приспособленности (fB) обновляется. Позиция общего лучшего решения (cB) обновляется, копируя координаты детёныша (childPos).

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

      //————————————————————————————————————————————————————————————————————
      void C_AO_BM::Revision ()
      {
        // Обновляем веса на основе текущего фитнеса
        UpdateWeights ();
      
        // Шаг 8: Swapping
        SwapWorstWithBest ();
      
        // Шаг 12: Update Current Best
        for (int i = 0; i < popSize; i++)
        {
          if (a [i].f > a [i].fB)
          {
            a [i].fB = a [i].f;
            ArrayCopy (a [i].cB, a [i].c, 0, 0, coords);
          }
      
          if (a [i].f > fB)
          {
            fB = a [i].f;
            ArrayCopy (cB, a [i].c, 0, 0, coords);
          }
        }
      
        // Проверка детёнышей для глобального лучшего
        for (int i = 0; i < numChildren; i++)
        {
          if (childFitness [i] > fB)
          {
            fB = childFitness [i];
            for (int j = 0; j < coords; j++)
            {
              cB [j] = childPos [i * coords + j];
            }
          }
        }
      }
      //————————————————————————————————————————————————————————————————————

      Метод "UpdatePositions" класса "C_AO_BM" реализует основную логику движения особей в популяции, следуя определенным математическим уравнениям. Метод разделен на две части: обновление позиций взрослых особей (так называемых "синих обезьян") и обновление позиций детёнышей.

      Обновление позиций взрослых особей (синих обезьян):

      Метод проходит по каждой взрослой особи "i" (от 0 до popSize  - 1). Поиск лидера группы: для каждой текущей особи определяется её группа (groupId [i]) и находится индекс лучшей особи в этой группе (bestIdx = FindGroupBest). Если текущая особь сама является лидером группы или лидер не найден, то переходим к следующей особи. Обновление по уравнениям 1 и 2 (для каждого измерения).

      Обновление скорости (Уравнение 1):

      • Определяются веса лидера группы "Wleader" и текущей особи "Wi".
      • Извлекаются текущие позиции лидера "Xbest" и текущей особи "Xi" в данном измерении.
      • Генерируется случайное число "rand1" от 0 до 1.
      • Рассчитывается новая скорость "monkeyRate" для текущей особи. Она является комбинацией предыдущей скорости (с коэффициентом 0.9) и влияния разницы весов лидера и текущей особи, умноженного на случайное число и разницу между позициями лидера и текущей особи.

      Обновление позиции (Уравнение 2):

      Генерируется еще одно случайное число "rand2" от 0 до 1. Новая позиция особи рассчитывается путем добавления к текущей позиции произведения новой скорости "monkeyRate" и случайного числа "rand2". Полученная новая позиция принудительно ограничивается заданными диапазонами (u.SeInDiSp), чтобы она оставалась в пределах допустимой области поиска.

        Обновление позиций детёнышей:

        1. Поиск лучшего детёныша: сначала метод просматривает всех детёнышей "i" от (0 до numChildren  - 1), чтобы найти того, у кого наилучшая приспособленность (bestChildFitness) и, соответственно, его индекс (bestChildIdx).
        2. Обновление по уравнениям 3 и 4 (если лучший детёныш найден):
          • Если лучший детёныш найден (bestChildIdx >= 0), метод проходит по всем остальным детёнышам "i" от (0 до numChildren  - 1), за исключением самого лучшего.
          • Обновление скорости детёныша (Уравнение 3):
            • Аналогично взрослым особям, определяются веса лучшего детёныша "Wchleader" и текущего детёныша "Wchi".
            • Извлекаются текущие позиции лучшего детёныша "Xchbest" и текущего детёныша "Xchi" в данном измерении.
            • Генерируется случайное число "rand1" от 0 до 1.
            • Рассчитывается новая скорость (childRate) для текущего детёныша по формуле, аналогичной Уравнению 1.
          • Обновление позиции детёныша (Уравнение 4):
            • Генерируется случайное число "rand2" от 0 до 1.
            • Новая позиция детёныша рассчитывается путем добавления к текущей позиции произведения его новой скорости (childRate) и случайного числа "rand2".
          • Ограничение позиции: полученная новая позиция детёныша также ограничивается заданными диапазонами.

        Таким образом, метод "UpdatePositions" реализует:

        • Модель поведения "синих обезьян": взрослые особи движутся, ориентируясь на лидера своей группы, при этом скорость движения зависит от разницы весов и разницы позиций.
        • Модель поведения детёнышей: детёныши движутся, ориентируясь на самого лучшего детёныша, аналогичным образом.
        • Поддержание особей в допустимой области поиска с помощью функции "SeInDiSp".
        //————————————————————————————————————————————————————————————————————
        void C_AO_BM::UpdatePositions ()
        {
          // Шаг 9: Update position of blue monkeys by Equations 1 and 2
          for (int i = 0; i < popSize; i++)
          {
            int bestIdx = FindGroupBest (groupId [i]);
            if (bestIdx < 0 || bestIdx == i) continue;
        
            for (int j = 0; j < coords; j++)
            {
              // Equation (1): Rate update
              double Wleader = monkeyWeight [bestIdx];
              double Wi      = monkeyWeight [i];
              double Xbest   = a [bestIdx].c [j];
              double Xi      = a [i].c [j];
              double rand1   = u.RNDfromCI (0, 1);
        
              // Rate_{i,t} = (0.7*Rate) + (W_leader - W_i) * rand*(X_best-X_i)
              monkeyRate [i * coords + j] = 0.9 * monkeyRate [i * coords + j] +
                                            (Wleader - Wi) * rand1 * (Xbest - Xi);
        
              // Equation (2): Position update
              // X_{i,t} = X_i + Rate_{i,t} * rand
              double rand2 = u.RNDfromCI (0, 1);
              a [i].c [j] = a [i].c [j] + monkeyRate [i * coords + j] * rand2;
        
              // Ограничение в пределах диапазона
              a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
            }
          }
        
          // Шаг 10: Update position of children by Equations 3 and 4
          // Находим лучшего детёныша
          int bestChildIdx = -1;
          double bestChildFitness = -DBL_MAX;
          for (int i = 0; i < numChildren; i++)
          {
            if (childFitness [i] > bestChildFitness)
            {
              bestChildFitness = childFitness [i];
              bestChildIdx = i;
            }
          }
        
          if (bestChildIdx >= 0)
          {
            for (int i = 0; i < numChildren; i++)
            {
              if (i == bestChildIdx) continue;
        
              for (int j = 0; j < coords; j++)
              {
                // Equation (3): Child rate update
                double Wchleader = childWeight [bestChildIdx];
                double Wchi      = childWeight [i];
                double Xchbest   = childPos [bestChildIdx * coords + j];
                double Xchi      = childPos [i * coords + j];
                double rand1     = u.RNDfromCI (0, 1);
        
                childRate [i * coords + j] = 0.9 * childRate [i * coords + j] +
                                             (Wchleader - Wchi) * rand1 * (Xchbest - Xchi);
        
                // Equation (4): Child position update
                double rand2 = u.RNDfromCI (0, 1);
                childPos [i * coords + j] = childPos [i * coords + j] + childRate [i * coords + j] * rand2;
        
                childPos [i * coords + j] = u.SeInDiSp (childPos [i * coords + j], rangeMin [j], rangeMax [j], rangeStep [j]);
              }
            }
          }
        }
        //————————————————————————————————————————————————————————————————————

        Метод "SwapWorstWithBest" выполняет следующие действия:

        Подготовка детёнышей. Создается массив "childIndices" для хранения индексов всех детёнышей. Этот массив заполняется индексами от 0 до общего числа детёнышей. Затем происходит сортировка индексов детёнышей по убыванию их значения приспособленности (childFitness). Это делается с помощью простого алгоритма пузырьковой сортировки. В результате "childIndices [0]" будет содержать индекс лучшего детёныша, "childIndices [1]" – индекс второго лучшего, и так далее. Создается булевый массив "childUsed" для отслеживания того, был ли каждый детёныш использован для замены.

        Итерация по группам взрослых особей. Метод проходит по каждой группе взрослых особей (от g = 0 до numGroups - 1). Для каждой группы, если все детёныши уже были использованы, дальнейшая обработка прекращается. Внутри каждой группы находится худшая взрослая особь (worstAdultIdx). Выбирается лучший доступный на данный момент детёныш (используя отсортированный массив "childIndices" и счетчик "childIndex"). Проверяется, лучше ли приспособленность выбранного детёныша (childFitness [currentChildIdx]) по сравнению с приспособленностью худшей взрослой особи в группе (a [worstAdultIdx].f).

        • Замена (если лучше):
          • Если детский лучше:
            • Позиция и скорость худшей взрослой особи заменяются позицией и скоростью выбранного детёныша.
            • Значение приспособленности взрослой особи обновляется до значения приспособленности детёныша.
            • Вес взрослой особи обновляется до веса детёныша.
            • Детёныш помечается как использованный (childUsed[currentChildIdx] = true), и счетчик "childIndex" увеличивается для перехода к следующему лучшему детёнышу.
        • Прекращение замены (если хуже):
          • Если даже лучший доступный детёныш не лучше, чем худшая взрослая особь в текущей группе, то дальнейшие попытки замены в этой и последующих группах прекращаются. Это связано с тем, что детёныши отсортированы по приспособленности, и если худший из лучших детёнышей не превосходит худшую особь, то и последующие детёныши не дадут выигрыша.
          Создание новых детёнышей. После того, как были выполнены все возможные замены, метод проходит по всем детёнышам. Если детёныш был использован для замены (childUsed [i] равно true) его позиция полностью перегенерируется в пределах заданных диапазонов (с использованием u.RNDfromCI и u.SeInDiSp), скорость сбрасывается на "0", значение приспособленности сбрасывается на минимальное возможное значение (указывающее на то, что он ещё не был оценен/обновлен). Ему назначается новый случайный вес в пределах начальных ограничений (minW, maxW).

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

            //————————————————————————————————————————————————————————————————————
            void C_AO_BM::SwapWorstWithBest ()
            {
              // Шаг 8: Swapping the worst fitness in each group by the best fitness in children group
            
              // Создаем массив индексов детёнышей и сортируем по фитнесу
              int childIndices [];
              ArrayResize (childIndices, numChildren);
              for (int i = 0; i < numChildren; i++)
              {
                childIndices [i] = i;
              }
            
              // Сортируем индексы детёнышей по убыванию фитнеса
              for (int i = 0; i < numChildren - 1; i++)
              {
                for (int j = 0; j < numChildren - i - 1; j++)
                {
                  if (childFitness [childIndices [j]] < childFitness [childIndices [j + 1]])
                  {
                    int temp = childIndices [j];
                    childIndices [j] = childIndices [j + 1];
                    childIndices [j + 1] = temp;
                  }
                }
              }
            
              // Для отслеживания использованных детёнышей
              bool childUsed [];
              ArrayResize (childUsed, numChildren);
              ArrayInitialize (childUsed, false);
            
              // Для каждой группы пытаемся заменить худшего
              int childIndex = 0;
            
              for (int g = 0; g < numGroups; g++)
              {
                if (childIndex >= numChildren) break;
            
                // Находим худшего в группе
                int worstAdultIdx = FindGroupWorst (g);
                if (worstAdultIdx < 0) continue;
            
                // Используем лучшего доступного детёныша
                int currentChildIdx = childIndices [childIndex];
            
                // Проверяем, лучше ли детёныш
                if (childFitness [currentChildIdx] > a [worstAdultIdx].f)
                {
                  // Выполняем замену
                  for (int j = 0; j < coords; j++)
                  {
                    a [worstAdultIdx].c [j] = childPos [currentChildIdx * coords + j];
                    monkeyRate [worstAdultIdx * coords + j] = childRate [currentChildIdx * coords + j];
                  }
                  a [worstAdultIdx].f = childFitness [currentChildIdx];
                  monkeyWeight [worstAdultIdx] = childWeight [currentChildIdx];
            
                  childUsed [currentChildIdx] = true;
                  childIndex++;
                }
                else
                {
                  // Если лучший детёныш не лучше худшего взрослого,
                  // дальше нет смысла проверять (они отсортированы)
                  break;
                }
              }
            
              // Генерируем новых детёнышей на место использованных
              for (int i = 0; i < numChildren; i++)
              {
                if (childUsed [i])
                {
                  for (int j = 0; j < coords; j++)
                  {
                    childPos [i * coords + j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
                    childPos [i * coords + j] = u.SeInDiSp (childPos [i * coords + j],
                                                            rangeMin [j], rangeMax [j], rangeStep [j]);
                    childRate [i * coords + j] = 0.0;
                  }
                  childFitness [i] = -DBL_MAX;
                  childWeight [i] = u.RNDfromCI (minW, maxW); // Новый вес согласно начальным условиям
                }
              }
            }
            //————————————————————————————————————————————————————————————————————

            Функция "UpdateWeights" отвечает за корректировку весов для двух групп сущностей: "обезьян" (родительских особей) и "детёнышей". В основу корректировки весов заложен принцип нормализации и масштабирования на основе приспособленности (fitness) каждой сущности.

            Определение диапазона приспособленности. Сначала функция находит минимальное (minFit) и максимальное (maxFit) значения приспособленности среди всех "обезьян" в популяции.

            Обработка случая с одинаковой приспособленностью. Если разница между максимальной и минимальной приспособленностью очень мала (меньше, чем 1e-10, что фактически означает, что все приспособленности примерно одинаковы), то всем "обезьянам" присваивается фиксированный вес, равный 5.0 (среднее значение из желаемого диапазона).

            Нормализация и масштабирование весов "обезьян". Если диапазон приспособленности достаточен (больше 1e-10), то для каждой "обезьяны" происходит следующее:

            • Ее приспособленность (a [i].f) нормализуется в диапазон [0, 1]. Это делается путем вычитания минимальной приспособленности (minFit) и деления на разницу между максимальной и минимальной приспособленностью (maxFit - minFit).
            • Полученное нормализованное значение затем масштабируется для получения конечного веса в диапазоне [4, 6]. Это достигается умножением нормализованного значения на 2.0 и прибавлением минимального веса (minW). Таким образом, особи с самой низкой приспособленностью получают вес, близкий к 4.0, а особи с самой высокой приспособленностью — вес, близкий к 6.0.

              Обработка весов "детёнышей". После обработки "обезьян", процесс повторяется для "детёнышей". Сначала определяется минимальная (minChildFit) и максимальная (maxChildFit) приспособленность среди всех "детёнышей". При этом игнорируются "детёныши", у которых приспособленность не была инициализирована (предполагается, что неинициализированная приспособленность имеет очень низкое значение, близкое к -DBL_MAX). Аналогично "обезьянам":

              • если разница между максимальной и минимальной приспособленностью "детёнышей" мала, всем инициализированным "детёнышам" присваивается вес 5.0;
              • если диапазон приспособленности "детёнышей" достаточен, их приспособленность нормализуется в диапазон [0, 1] и затем масштабируется в диапазон [4, 6] аналогично "обезьянам".

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

                //————————————————————————————————————————————————————————————————————
                void C_AO_BM::UpdateWeights ()
                {
                  double minFit = DBL_MAX, maxFit = -DBL_MAX;
                
                  for (int i = 0; i < popSize; i++)
                  {
                    if (a [i].f > maxFit) maxFit = a [i].f;
                    if (a [i].f < minFit) minFit = a [i].f;
                  }
                
                  if (maxFit - minFit > 1e-10)
                  {
                    for (int i = 0; i < popSize; i++)
                    {
                      // Нормализуем в [0,1] и масштабируем в [4,6]
                      double normalized = (a [i].f - minFit) / (maxFit - minFit);
                      monkeyWeight [i] = minW + normalized * 2.0; // Получаем значение в [4,6]
                    }
                  }
                  else
                  {
                    for (int i = 0; i < popSize; i++)
                    {
                      monkeyWeight [i] = 5.0; // Среднее значение
                    }
                  }
                
                  // Обновление весов детёнышей
                  double minChildFit = DBL_MAX, maxChildFit = -DBL_MAX;
                
                  for (int i = 0; i < numChildren; i++)
                  {
                    if (childFitness [i] > -DBL_MAX + 1) // Проверка на инициализацию
                    {
                      if (childFitness [i] > maxChildFit) maxChildFit = childFitness [i];
                      if (childFitness [i] < minChildFit) minChildFit = childFitness [i];
                    }
                  }
                
                  if (maxChildFit - minChildFit > 1e-10)
                  {
                    for (int i = 0; i < numChildren; i++)
                    {
                      if (childFitness [i] > -DBL_MAX + 1)
                      {
                        double normalized = (childFitness [i] - minChildFit) /
                                            (maxChildFit - minChildFit);
                        childWeight [i] = minW + normalized * 2.0; // В диапазоне [4,6]
                      }
                    }
                  }
                  else
                  {
                    for (int i = 0; i < numChildren; i++)
                    {
                      if (childFitness [i] > -DBL_MAX + 1)
                      {
                        childWeight [i] = 5.0;
                      }
                    }
                  }
                }
                //————————————————————————————————————————————————————————————————————

                Функция "FindGroupBest" предназначена для поиска лучшей особи в пределах определенной группы. Функция проходит по каждой особи в общей популяции, используя цикл (от 0 до popSize - 1).

                  Проверка принадлежности к группе и сравнение приспособленности. Для каждой особи проверяется, принадлежит ли она к целевой группе и если особь принадлежит к нужной группе, выполняется проверка ее приспособленности (a [i].f). Если текущая приспособленность (a [i].f) больше, чем текущее максимальное значение приспособленности (bestFitness), то:
                  • bestFitness обновляется до значения приспособленности текущей особи.
                  • bestIdx обновляется до индекса текущей особи "i".

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

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

                  //————————————————————————————————————————————————————————————————————
                  int C_AO_BM::FindGroupBest (int grpId)
                  {
                    int bestIdx = -1;
                    double bestFitness = -DBL_MAX;
                  
                    for (int i = 0; i < popSize; i++)
                    {
                      if (groupId [i] == grpId && a [i].f > bestFitness)
                      {
                        bestFitness = a [i].f;
                        bestIdx = i;
                      }
                    }
                  
                    return bestIdx;
                  }
                  //————————————————————————————————————————————————————————————————————

                  Функция "FindGroupWorst" проходит по каждой особи, начиная с индекса 0 и заканчивая последней особью в популяции (popSize - 1).

                  Проверка принадлежности к группе и сравнение приспособленности. Для каждой особи в цикле сначала проверяется, принадлежит ли она к целевой группе, заданной параметром "grpId". Если особь принадлежит к интересующей нас группе, то выполняется сравнение ее значения приспособленности (a [i].f) с текущим наихудшим значением (worstFitness). Если приспособленность текущей особи меньше, чем текущее наихудшее значение (worstFitness), это означает, что мы нашли новую, ещё более плохую особь. В таком случае:

                  • worstFitness обновляется до значения приспособленности этой новой, более плохой особи;
                  • worstIdx обновляется, чтобы хранить индекс этой новой, более плохой особи "i".
                  После того как цикл завершится (то есть будут проверены все особи в популяции), функция вернет значение "worstIdx". Это будет индекс самой худшей особи в указанной группе. Если группа окажется пустой, то будет возвращено начальное значение -1. Иными словами, функция "FindGroupWorst" находит и возвращает индекс особи с наименьшим значением приспособленности в рамках заданного идентификатора группы.

                  //————————————————————————————————————————————————————————————————————
                  int C_AO_BM::FindGroupWorst (int grpId)
                  {
                    int worstIdx = -1;
                    double worstFitness = DBL_MAX;
                  
                    for (int i = 0; i < popSize; i++)
                    {
                      if (groupId [i] == grpId && a [i].f < worstFitness)
                      {
                        worstFitness = a [i].f;
                        worstIdx = i;
                      }
                    }
                  
                    return worstIdx;
                  }
                  //————————————————————————————————————————————————————————————————————


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

                  Посмотрим на результаты, к сожалению, этого недостаточно, чтобы попасть в турнирную таблицу. 

                  BM|Blue Monkey Algorithm|50.0|3.0|0.7|
                  =============================
                  5 Hilly's; Func runs: 10000; result: 0.6730156841791012
                  25 Hilly's; Func runs: 10000; result: 0.3811383925125479
                  500 Hilly's; Func runs: 10000; result: 0.26883473381494066
                  =============================
                  5 Forest's; Func runs: 10000; result: 0.6116553050534701
                  25 Forest's; Func runs: 10000; result: 0.3051920014986885
                  500 Forest's; Func runs: 10000; result: 0.18691769556662757
                  =============================
                  5 Megacity's; Func runs: 10000; result: 0.4384615384615385
                  25 Megacity's; Func runs: 10000; result: 0.19353846153846152
                  500 Megacity's; Func runs: 10000; result: 0.1037384615384623
                  =============================
                  All score: 3.16249 (35.14%)

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

                  Hilly

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

                  Forest

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

                  Megacity

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

                  В рейтинговой таблице алгоритм BM представлен ознакомительно.

                  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)
                  DOAdingomdingo_optimization_algorithm_M0,479680,453670,463691,397040,941450,879090,914542,735080,786150,860610,848052,494816,62773,63
                  ANSacross neighbourhood search0,949480,847760,438572,235811,000000,923340,399882,323230,709230,634770,230911,574916,13468,15
                  CLAcode lock algorithm (joo)0,953450,871070,375902,200420,989420,917090,316422,222940,796920,693850,193031,683806,10767,86
                  AMOmanimal migration ptimization M0,903580,843170,462842,209590,990010,924360,465982,380340,567690,591320,237731,396755,98766,52
                  (P+O)ES(P+O) evolution strategies0,922560,881010,400212,203790,977500,874900,319452,171850,673850,629850,186341,490035,86665,17
                  CTAcomet tail algorithm (joo)0,953460,863190,277702,094350,997940,857400,339492,194840,887690,564310,105121,557125,84664,96
                  TETAtime evolution travel algorithm (joo)0,913620,823490,319902,057010,970960,895320,293242,159520,734620,685690,160211,580525,79764,41
                  SDSmstochastic diffusion search M0,930660,854450,394762,179880,999830,892440,196192,088460,723330,611000,106701,441035,70963,44
                  BOAmbilliards optimization algorithm M0,957570,825990,252352,035901,000000,900360,305022,205380,735380,525230,095631,356255,59862,19
                  AAmarchery algorithm M0,917440,708760,421602,047800,925270,758020,353282,036570,673850,552000,237381,463235,54861,64
                  ESGevolution of social groups (joo)0,999060,796540,350562,146161,000000,828630,131021,959650,823330,553000,047251,423585,52961,44
                  SIAsimulated isotropic annealing (joo)0,957840,842640,414652,215130,982390,795860,205071,983320,686670,493000,090531,270205,46960,76
                  EOmextremal_optimization_M0,761660,772420,317471,851550,999990,767510,235272,002770,747690,539690,142491,429875,28458,71
                  BBObiogeography based optimization0,949120,694560,350311,993990,938200,673650,256821,868670,746150,482770,173691,402615,26558,50
                  ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
                  DAdialectical algorithm0,861830,700330,337241,899400,981630,727720,287181,996530,703080,452920,163671,319675,21657,95
                  BHAmblack hole algorithm M0,752360,766750,345831,864930,935930,801520,271772,009230,650770,516460,154721,321955,19657,73
                  ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
                  RFOroyal flush optimization (joo)0,833610,737420,346291,917330,894240,738240,240981,873460,631540,502920,164211,298675,08956,55
                  AOSmatomic orbital search M0,802320,704490,310211,817020,856600,694510,219961,771070,746150,528620,143581,418355,00655,63
                  TSEAturtle shell evolution algorithm (joo)0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
                  BSAbacktracking_search_algorithm0,973090,545340,290981,809410,999990,585430,217471,802890,847690,369530,129781,347004,95955,10
                  DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
                  SRAsuccessful restaurateur algorithm (joo)0,968830,634550,292171,895550,946370,555060,191241,692670,749230,440310,125261,314804,90354,48
                  CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
                  BIOblood inheritance optimization (joo)0,815680,653360,308771,777810,899370,653190,217601,770160,678460,476310,139021,293784,84253,80
                  DOAdream_optimization_algorithm0,855560,700850,372801,929210,734210,489050,241471,464730,772310,473540,185611,431464,82553,62
                  BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
                  DEAdolphin_echolocation_algorithm0,759950,675720,341711,777380,895820,642230,239411,777460,615380,440310,151151,206844,76252,91
                  HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
                  SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
                  BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
                  ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
                  (PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
                  FBAfractal-based Algorithm0,790000,651340,289651,730990,871580,568230,188771,628580,610770,460620,123981,195374,55550,61
                  TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
                  BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
                  WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
                  AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
                  AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
                  CAmcamel algorithm M0,786840,560420,351331,698590,827720,560410,243361,631490,648460,330920,134181,113564,44449,37
                  ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
                  CMAEScovariance_matrix_adaptation_evolution_strategy0,762580,720890,000001,483470,820560,796160,000001,616720,758460,490770,000001,249234,34948,33
                  DA_duelistduelist_algorithm0,927820,537780,277921,743520,869570,475360,181931,526860,621530,335690,117151,074374,34548,28
                  BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
                  BMblue_monkey _algorithm0,673010,381130,268831,322970,611650,305190,186911,103750,438460,193540,103730,735733,16235,14
                  RWrandom walk0,487540,321590,257811,066940,375540,219440,158770,753750,279690,149170,098470,527342,34826,09


                  Выводы

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

                  Алгоритм Blue Monkey представляет интересный подход к решению оптимизационных задач, основанный на моделировании природных процессов. Несмотря на текущие ограничения производительности, концепция имеет потенциал для развития. Текущая версия может быть рекомендована для образовательных целей и как основа для дальнейших исследований в области метаэвристических алгоритмов оптимизации.

                  tab

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

                  chart

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

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

                  Плюсы:

                  1. Небольшое количество внешних параметров.

                  Минусы:

                  1. Низкая эффективность на задачах, особенно на дискретных функциях.

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


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

                  #ИмяТипОписание
                  1#C_AO.mqh
                  Включаемый файл
                  Родительский класс популяционных алгоритмов оптимизации
                  2#C_AO_enum.mqh
                  Включаемый файл
                  Перечисление популяционных алгоритмов оптимизации
                  3TestFunctions.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_BM.mq5
                  СкриптИспытательный стенд для BM
                  Прикрепленные файлы |
                  BM.ZIP (348.08 KB)
                  Генеративно-состязательные сети (GAN) для синтетических данных в сфере финансового моделирования (Часть 2): Создание синтетического символа для тестирования Генеративно-состязательные сети (GAN) для синтетических данных в сфере финансового моделирования (Часть 2): Создание синтетического символа для тестирования
                  В этой статье мы создаем синтетический символ с использованием генеративно-состязательной сети (GAN), которая включает в себя генерацию реалистичных финансовых данных, имитирующих поведение реальных рыночных инструментов, таких как EURUSD. Модель GAN изучает закономерности и волатильность на основе исторических рыночных данных и создает синтетические ценовые данные с аналогичными характеристиками.
                  Автоматизация торговых стратегий на MQL5 (Часть 10): Разработка стратегии Trend Flat Momentum Автоматизация торговых стратегий на MQL5 (Часть 10): Разработка стратегии Trend Flat Momentum
                  В настоящей статье мы разрабатываем советник на MQL5 для стратегии Trend Flat Momentum. Мы комбинируем пересечение двух скользящих средних с фильтрами импульса RSI и CCI для генерации торговых сигналов. Также рассказываем о тестировании на истории и потенциальных улучшениях для повышения эффективности в реальных условиях.
                  Разрабатываем менеджер терминалов (Часть 1): Постановка задачи Разрабатываем менеджер терминалов (Часть 1): Постановка задачи
                  Как обеспечить возможность удобного контроля за несколькими терминалами, на которых торгуют советники, да ещё и на разных компьютерах? Попробуем создать веб-интерфейс по управлению запуском торговых терминалов MetaTrader 5 и просмотру детальной информации о работе каждого экземпляра.
                  От начального до среднего уровня: Индикатор (I) От начального до среднего уровня: Индикатор (I)
                  В этой статье мы создадим наш первый индикатор, который будет полностью практичным и функциональным. Цель не в том, чтобы показать, как создать приложение, а в том, чтобы помочь вам понять, как можно развивать собственные идеи и дать вам возможность применить их на практике безопасным, простым и практичным способом.