preview
Алгоритм оптимизации грифов — Buzzard Optimization Algorithm (BUZOA)

Алгоритм оптимизации грифов — Buzzard Optimization Algorithm (BUZOA)

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

Содержание

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


Введение

В арсенале популяционных алгоритмов оптимизации существует метод роя частиц (PSO), на котором построено большинство современных гибридов. Базовая схема PSO известна десятилетиями, её сильные и слабые стороны изучены, и попытки её улучшить, как правило, идут в одном направлении: усложнить математику, добавить адаптивные коэффициенты, ввести новые операторы. Но что если посмотреть на задачу с другой стороны — не усложнить уравнения, а изменить саму логику принятия решений агентом? Что если каждая частица в каждый момент времени будет случайно выбирать одну из нескольких качественно разных стратегий поведения, как это делают живые существа в природе? Именно такой ход придумали авторы BUZOA, и вопрос в том, действительно ли биологически мотивированное переключение режимов даёт что-то, чего нет у классических PSO-подобных схем — или это лишь красивая метафора без практической ценности.

Читатель получит полностью разобранный, реализованный на MQL5 и протестированный в составе нашего стенда метаэвристический алгоритм BUZOA — с детальным разбором его трёх режимов охоты, сравнением буквальных формул статьи Аршаги, Ашурияна и Габели (2019) с их рациональной интерпретацией, бенчмарком на тестовых функциях Hilly, Forest и Megacity, и рекомендациями по настройке параметров. Параллельно читатель увидит, как биологическая аналогия — зрение, обоняние и слежение за хищниками — переводится в математический язык скорости, инерции и случайного рассеивания, и сможет оценить, насколько эта аналогия работает на практике.


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

Грифы — одни из немногих животных, выработавших коллективную стратегию поиска пищи, опирающуюся сразу на два независимых канала информации: обоняние и зрение. Индюшачьи грифы (Cathartes aura) обладают исключительно развитым обонянием и способны учуять падаль с расстояния более километра. Чёрные грифы (Coragyps atratus) практически лишены этого чувства, однако компенсируют его острым зрением: они не ищут пищу напрямую, а следят за поведением индюшачьих сородичей сверху. Как только индюшачий гриф снижается, чёрные грифы пикируют следом и нередко оттесняют первооткрывателя.

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

Каждый агент популяции представляет одного грифа, характеризуемого двумя векторами. Первый — вектор позиции L, задающий текущее местоположение птицы в пространстве поиска. Второй — вектор способности C (ability vector), который по аналогии со скоростью в методе роя частиц (PSO) определяет, насколько и в каком направлении агент сместится на следующем шаге. Такое разделение позволяет сохранять «накопленный импульс» движения: гриф не останавливается резко, а плавно меняет траекторию под влиянием новой информации.

Каждый агент помнит лучшую позицию, которую он когда-либо занимал (личный рекорд pBest), а вся стая знает глобально лучшую найденную позицию gBest. Вектор способности ограничен диапазоном [−C_max, +C_max], где C_max = (L_max − L_min) / 2, то есть не превышает половины ширины пространства поиска по каждому измерению. Это предотвращает неконтролируемый разгон агентов и удерживает их в разумных границах.

Инициализация. В начале работы каждый агент получает случайную позицию, равномерно распределённую по всему пространству поиска, и случайную начальную способность из диапазона [−C_max, +C_max]. Такая инициализация обеспечивает максимальное начальное разнообразие популяции.

Рассмотрим простой пример в одномерном пространстве на отрезке [0, 100]. Пусть C_max = 50. Агент A получил начальную позицию L = 30 и начальную способность C = +12, агент B — позицию L = 70 и способность C = −8. Без какого-либо обновления агент A сместился бы в сторону увеличения координаты, агент B — в сторону уменьшения.

Три режима охоты. На каждой итерации для каждого агента независимо разыгрывается случайное равновероятное число в диапазоне [0, 1], определяющее, по какому из трёх сценариев он будет действовать в текущем шаге. Все три режима активны одновременно в разных частях популяции, что и создаёт баланс между сходимостью и разнообразием.

Режим 001 — эксплуатация. В этом режиме гриф охотится самостоятельно, без ориентации на соседей. Единственный ориентир — его собственный лучший результат pBest. Обновление способности описывается уравнением:

C(t) = α₁ · C(t−1) + c1 · r · (pBest − L)

Первый член, α₁ · C(t−1), сохраняет инерцию предыдущего движения: если птица летела вправо, она продолжит лететь вправо, но несколько медленнее (α₁ < 1). Второй член тянет агента к его личному рекорду: разность (pBest − L) показывает, насколько далеко и в каком направлении находится лучшая известная позиция, а случайный множитель r ∈ [0, 1] придаёт этому притяжению стохастический характер.

Например, агент с L = 30, C = +12, pBest = 45 при α₁ = 0.75, c1 = 1.5, r = 0.6 получит новую способность C = 0.75 · 12 + 1.5 · 0.6 · (45 − 30) = 9 + 13.5 = 22.5. После ограничения C_max = 50 значение не изменится, и новая позиция составит L = 30 + 22.5 = 52.5. Агент стремительно движется к своему рекорду.

Режим 010 — исследование через запах. Этот режим моделирует поведение стаи под руководством индюшачьего грифа. Вычисление выполняется в два этапа. Сначала рассчитывается базовый импульс C₁ по той же формуле, что и в режиме 001: агент учитывает инерцию и притяжение к личному рекорду. Затем к C₁ добавляются два дополнительных слагаемых:

C(t) = C₁ + c1 · r₁ · (pBest − L) + c2 · r₂ · (gBest − L)

Первое дополнительное слагаемое — повторное когнитивное притяжение к pBest, второе — социальное притяжение к глобальному рекорду gBest. Оба члена записаны через разности позиций, что обеспечивает размерную согласованность: скорость формируется из разностей координат, а не из разнородных величин.

Продолжим пример. Пусть агент имеет L = 30, C₁ = 22.5 (из предыдущего шага), pBest = 45, gBest = 80, r₁ = 0.4, r₂ = 0.7. Тогда C = 22.5 + 1.5 · 0.4 · (45 − 30) + 1.5 · 0.7 · (80 − 30) = 22.5 + 9 + 52.5 = 84. После обрезки до C_max = 50 получаем C = 50, новая позиция L = 30 + 50 = 80. Агент резко тянется к глобальному лидеру — именно такое поведение описывает погоню за обнаруженной падалью.

Режим 010 является наиболее мощным с точки зрения исследования: одновременное притяжение к pBest и gBest может увести агента далеко от текущей позиции, что особенно ценно в начале оптимизации, когда популяция ещё не сконцентрировалась.

Режим 100 — глобальное рассеивание. Этот режим моделирует поведение чёрного грифа, заметившего скопление хищников в совершенно другом районе. Агент бросает текущее место и немедленно перелетает туда. В алгоритме это реализуется как телепортация в случайную точку всего пространства поиска, а вектор способности при этом обнуляется — птица «приземлилась» заново и пока неподвижна.

C = 0, L = случайное из [L_min, L_max]

Если в нашем примере агент с L = 30 попадает в режим 100, он может оказаться, например, в точке L = 67, начав следующую итерацию с нулевой скоростью. Это самый радикальный из трёх режимов: агент полностью теряет накопленную историю движения, но зато исследует область, которая иначе была бы недоступна. Режим 100 является основным механизмом выхода из локальных оптимумов — благодаря ему стая не застревает навсегда в удачно найденной, но не глобальной ямке.

Обновление позиции и затухание инерции. После расчёта новой способности в режимах 001 и 010 позиция обновляется по простому правилу:

L(t) = L(t−1) + C(t)

Позиция не может выйти за границы допустимого диапазона: если L + C превышает L_max или опускается ниже L_min, значение обрезается до соответствующей границы. Аналогичное ограничение применяется к самой способности.

В конце каждой итерации коэффициент инерции α₁ немного уменьшается: α₁ ← α₁ · wDamp, где wDamp ≈ 0.99. Это плавный переход от исследования к эксплуатации по мере работы алгоритма: на ранних итерациях агенты сохраняют большую часть импульса и активно перемещаются по пространству, а ближе к концу всё больше «прижимаются» к своим рекордам.

Параметры алгоритма. Алгоритм имеет пять настраиваемых параметров. Размер популяции popSize определяет количество агентов; значение по умолчанию 50 является хорошим компромиссом между качеством поиска и вычислительными затратами. Коэффициент инерции alpha1 ∈ [0.7, 0.8] задаёт начальную «память о прошлом движении»; слишком большое значение даёт излишне инерционных агентов, слишком малое — хаотичных. Когнитивный и социальный коэффициенты c1 и c2 управляют силой притяжения к личному и глобальному рекордам соответственно; симметричные значения означают равный вес обоих ориентиров. Коэффициент затухания wDamp контролирует скорость ослабления инерции.

Связь с методом роя частиц. BUZOA является концептуальным развитием PSO. В классическом PSO скорость частицы определяется тремя слагаемыми: инерция, притяжение к личному рекорду и притяжение к глобальному рекорду, — причём все три действуют на каждом шаге для каждой частицы. В BUZOA эти три компонента разнесены по трём режимам, которые активируются стохастически: режим 001 реализует чистую инерционно-когнитивную динамику, режим 010 добавляет полноценное социальное взаимодействие, а режим 100 вводит принципиально новый элемент — полный сброс позиции, аналогов которому в стандартном PSO нет. Такое разделение позволяет алгоритму более гибко управлять балансом между исследованием и эксплуатацией в зависимости от текущего состояния каждого агента. На иллюстрации ниже рассмотрим принцип действия агентов в алгоритме.

BUZOA

Рисунок 1. Иллюстрация работы алгоритма BUZOA

  • В центре — пространство поиска с глобальным лучшим (красная сфера g* — «добыча»), три жёлтых маркера p* — личные рекорды агентов.
  • Зелёные стрелки (агент в режиме 001) — притяжение только к собственному p*, узкий локальный поиск.
  • Синие стрелки (режим 010) — двойное притяжение и к p*, и к g*, классический PSO-шаг. Показано на двух агентах.
  • Фиолетовая пунктирная стрелка (режим 100) — телепортация в случайную точку с обнулением скорости, кружок-цель показывает место приземления.
  • Три цветных бокса по периметру — описание каждого режима с упрощённой формулой обновления способности.
  • Подпись «random mode each step» в верхней части — ключевая идея, что выбор режима случаен на каждой итерации.

Переходим к написанию псевдокода.

Algorithm: BUZOA — Buzzard Optimization Algorithm
Input:
    popSize         — population size
    coords          — problem dimensionality
    [L_min, L_max]  — search-space bounds per coordinate
    α₁              — inertia coefficient            [0.70.8]
    c₁              — cognitive coefficient (smell)  [1.51.7]
    c₂              — social coefficient   (vision)  [1.51.7]
    w_damp          — inertia decay per iteration    (≈ 0.99)
    epochs          — iteration budget
Output:
    g*, f(g*)       — best solution found

 INITIALISATION
1.  for each coordinate c = 1 … coords do
2.      C_max[c] ← (L_max[c] − L_min[c]) / 2                    // eq. (14)

3.  for each agent i = 1 … popSize do
4.      for each coordinate c = 1 … coords do
5.          L[i,c] ← uniform(L_min[c], L_max[c])               // random position
6.          C[i,c] ← uniform(−C_max[c], +C_max[c])             // random ability
7.      f*[i] ← −∞                                              // personal best fitness
8.  f(g*) ← −∞                                                   // global best fitness
9.  α₁_cur ← α₁ 
                                                 // working inertia
 MAIN LOOP
10. for t = 1 … epochs do

        // ---------- Evaluate population ----------
11.     for each agent i do
12.         f[i] ← FitnessFunction( L[i] )

        // ---------- Update personal & global bests ----------
13.     for each agent i do
14.         if f[i] > f*[i] then
15.             f*[i]  ← f[i]
16.             p*[i]  ← L[i]
17.         if f[i] > f(g*) then
18.             f(g*)  ← f[i]
19.             g*     ← L[i]

        // ---------- Move every agent ----------
20.     for each agent i do
21.         rMode ← uniform(0, 1)        // one die roll per agent, all coords share it

22.         for each coordinate c do

23.             if rMode < 1/3 then
                    // ─── MODE 001 — exploitation (eq. 8) ───
24.                 r ← uniform(0, 1)
25.                 C[i,c] ← α₁_cur · C[i,c]
                            + c₁ · r · ( p*[i,c] − L[i,c] )

26.             else if rMode < 2/3 then
                    // ─── MODE 010 — exploration (eq. 9, rational form) ───
27.                 r, r₁, r₂ ← uniform(0, 1)
28.                 C[i,c] ← α₁_cur · C[i,c]
                            + c₁ · r  · ( p*[i,c] − L[i,c] )
                            + c₁ · r₁ · ( p*[i,c] − L[i,c] )
                            + c₂ · r₂ · ( g*[c]   − L[i,c] )

29.             else
                    // ─── MODE 100 — global restart ───
30.                 C[i,c] ← 0
31.                 L[i,c] ← uniform(L_min[c], L_max[c])
32.                 continue                          // skip clamps & shift below

                // ---------- Clamp ability and shift position ----------
33.             C[i,c] ← clip( C[i,c], −C_max[c], +C_max[c] )      // eq. (13)
34.             L[i,c] ← clip( L[i,c] + C[i,c], L_min[c], L_max[c] ) // eq. (10)

        // ---------- Decay inertia ----------
35.     α₁_cur ← α₁_cur · w_damp

36. return g*, f(g*)

Теперь можем написать реализацию. Класс наследуется от базового C_AO, от которого получает стандартную инфраструктуру популяционного алгоритма: массив агентов a[], векторы глобального лучшего решения cB[]/fB, диапазоны пространства поиска, размерность задачи coords, утилитарный объект с генераторами случайных чисел и функциями отображения. Конструктор устанавливает метаданные алгоритма и значения параметров по умолчанию. 

Выбор значений соответствует рекомендациям статьи: α₁ — в середине рекомендованного интервала [0.7, 0.8] (инерция способности), c₁ и c₂ — в нижней границе интервала [1.5, 1.7] для когнитивного и социального коэффициентов, wDamp = 0.99 — стандартное для PSO-подобных методов значение коэффициента затухания инерции. popSize = 50 — компромисс между разнообразием популяции и количеством итераций при фиксированном бюджете вычислений.

Далее создаётся массив params[5] с именами и начальными значениями, через который оболочка тестового стенда может изменять настройки алгоритма извне.

Обратная операция к конструктору — извлекает значения из массива params[] и записывает их в типизированные переменные класса. Вызывается после того, как внешний код (обычно тестовый скрипт) перезаписал params[i].val. Без этого вызова алгоритм будет продолжать работать со значениями по умолчанию из конструктора.

Поля класса. Публичные поля alpha1, c1, c2, wDamp соответствуют коэффициентам α₁, α₂, γ из уравнений (8)–(9) статьи (параметр c1 в реализации выполняет роль α₂ из статьи, c2 — роль γ, имена выбраны для согласия с соглашениями PSO-литературы).

Приватные поля:

  • C[] — одномерный массив размера popSize × coords, хранит векторы способности всех агентов. Доступ через вспомогательный метод Idx(i, c), который вычисляет линейный индекс. Это эквивалент Cᵢ из уравнения (2) статьи.
  • Cmax[] — верхняя граница модуля способности, отдельная для каждой координаты. Реализует ограничение скорости из уравнения (14).
  • a1Cur — текущее значение инерции с учётом накопленного затухания. Отделено от alpha1, чтобы базовое значение не изменялось от прогона к прогону.

Вспомогательные методы Idx (линеаризация двумерного индекса) и Clamp (ограничение значения в заданном диапазоне) нужны для компактности основного кода.

//+------------------------------------------------------------------+
class C_AO_BUZOA : public C_AO
 {
public:
                    ~C_AO_BUZOA() {}
                     C_AO_BUZOA()
   {
    ao_name = "BUZOA";
    ao_desc = "Buzzard Optimization Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/21843";

    popSize = 50;
    alpha1  = 0.75;
    c1      = 1.5;
    c2      = 1.5;
    wDamp   = 0.1;

    ArrayResize(params, 5);
    params [0].name = "popSize";
    params [0].val = popSize;
    params [1].name = "alpha1";
    params [1].val = alpha1;
    params [2].name = "c1";
    params [2].val = c1;
    params [3].name = "c2";
    params [3].val = c2;
    params [4].name = "wDamp";
    params [4].val = wDamp;
   }

  void               SetParams()
   {
    popSize = (int)params [0].val;
    alpha1  = params      [1].val;
    c1      = params      [2].val;
    c2      = params      [3].val;
    wDamp   = params      [4].val;
   }

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

  void               Moving();
  void               Revision();

  //------------------------------------------------------------------
  double             alpha1; // коэффициент инерции способности    [0.7–0.8]
  double             c1;     // когнитивный коэффициент (запах)    [1.5–1.7]
  double             c2;     // социальный коэффициент  (зрение)   [1.5–1.7]
  double             wDamp;  // затухание инерции за итерацию      [≈0.99]

private: //----------------------------------------------------------
  double C           []; // векторы способности     [popSize × coords]
  double Cmax        []; // предел скорости: C ∈ [−Cmax, +Cmax]  ур. (14)
  double             a1Cur;   // текущий α₁ (убывает с каждой итерацией)

  int                Idx(int i, int c)
   {
    return i * coords + c;
   }
  double             Clamp(double v, double lo, double hi)
   {
    return v < lo ? lo : v > hi ? hi : v;
   }
 };
//+------------------------------------------------------------------+

Метод инициализации вызывается один раз перед началом оптимизации. Он получает границы и дискретизацию пространства поиска, после чего выполняет три действия.

Стандартная инициализация базового класса. Вызов StandardInit копирует диапазоны во внутренние массивы, создаёт популяцию a[popSize], обнуляет глобальный лучший фитнес. Если инициализация невозможна (например, несовпадение размеров массивов диапазонов), метод возвращает false.

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

Случайная инициализация популяции. Для каждого агента и каждой координаты:

  • Позиция cP[c] — равномерно из [rangeMin[c], rangeMax[c]] .
  • Способность C[i,c] — равномерно из [−Cmax[c], +Cmax[c]] .

Наконец, a1Cur = alpha1 — устанавливается начальное значение инерции для нового прогона.

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

  ArrayResize(C,    popSize * coords);
  ArrayResize(Cmax, coords);

//--- Предел скорости по ур. (14): C_max = (L_max − L_min) / 2
  for(int c = 0; c < coords; c++)
    Cmax [c] = (rangeMax [c] - rangeMin [c]) * 0.5;

//--- Случайная инициализация позиций и скоростей
  for(int i = 0; i < popSize; i++)
    for(int c = 0; c < coords; c++)
     {
      a [i].cP [c]   = u.RNDfromCI(rangeMin [c], rangeMax [c]);
      C [Idx(i, c)] = u.RNDfromCI(-Cmax [c], Cmax [c]);
     }

  a1Cur = alpha1;
  return true;
 }
//+------------------------------------------------------------------+

Метод Moving служит мостом между внутренним представлением положения агента (непрерывное cP[], где происходят вычисления скорости и позиции) и представлением, передаваемым на вход фитнес-функции (c[], приведённое к сетке дискретизации через SeInDiSp).

Разделение на cP и c принципиально для BUZOA: арифметика уравнений (8)–(10) требует непрерывных значений — при дискретизации разности (pBest − L) теряли бы смысл на мелких шагах. Поэтому физика алгоритма идёт в непрерывном пространстве, а дискретизация применяется только при подаче координат на оценку.

//+------------------------------------------------------------------+
void C_AO_BUZOA::Moving()
 {
  for(int i = 0; i < popSize; i++)
   {
    for(int c = 0; c < coords; c++)
     {
      a [i].c [c] = u.SeInDiSp(a [i].cP [c], rangeMin  [c], rangeMax  [c], rangeStep [c]);
     }
   }
 }
//+------------------------------------------------------------------+

Revision — основной шаг алгоритма. Это сердце BUZOA. Метод вызывается после оценки фитнеса всех агентов и выполняет три последовательных действия.

Обновление личных и глобального рекордов. Для каждого агента проверяется, улучшил ли он свой личный рекорд fB (лучший фитнес, найденный этим агентом за всю историю). Если да — обновляется пара fB/cB. Аналогично для глобального рекорда класса. Сравнение соответствует задаче максимизации; направление оптимизации унаследовано от базового класса C_AO. Координаты копируются из a[i].c (уже дискретизированных), что гарантирует, что лучшая точка всегда соответствует реально оценённой позиции.

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

Режим 001 — эксплуатация (rMode < 0.333): это прямая реализация уравнения (8) из статьи. В кодовой нотации α₁ → a1Cur, α₂ → c1, L*ᵢ → pBest . Формула описывает ситуацию, когда грифы охотятся сами: движение сохраняет инерцию и мягко корректируется в сторону личного рекорда. Радиус поиска ограничен, отсюда название «exploitation».

Режим 010 — исследование через запах (0.333 ≤ rMode < 0.667): рациональная интерпретация уравнения (9). Буквальная запись статьи содержит слагаемые β·(Cs − L) + (1−β)·(Cv − L) + γ·(gBest − L) с β ∈ [1.5, 1.7]. При таких значениях коэффициент (1−β) становится отрицательным, что даёт физически бессмысленное отталкивание от Cv. Очевидный дефект записи статьи, поэтому реализация отождествляет Cs с pBest, Cv с gBest, а нелогичную пару β/(1−β) заменяет на две положительные весовые функции. Результирующая формула — полный PSO-шаг: инерция + притяжение к личному рекорду + притяжение к глобальному рекорду. Сохраняется биологическая семантика: гриф-разведчик ведёт стаю к добыче, каждая птица одновременно следует и собственному опыту, и лидеру.

Режим 100 — глобальное рассеивание (rMode ≥ 0.667): чёрный гриф замечает добычу или хищника издалека и летит прямо туда. Формулы для этого режима в статье нет, только словесное описание «широкий диапазон поиска». Реализация — явная случайная ре-инициализация позиции по всему пространству с обнулением накопленной способности. continue пропускает стандартный шаг обновления, так как позиция уже задана напрямую. Это самый разрушительный из трёх режимов — периодические рестарты отдельных агентов защищают алгоритм от застревания в локальных максимумах.

Ограничение скорости и обновление позиции (для режимов 001 и 010): первая строка реализует уравнение (13) — ограничение способности диапазоном [−Cmax, +Cmax]. Вторая сохраняет новое значение способности в массив. Третья применяет уравнение (10): L(t) = L(t−1) + C(t), с дополнительным клиппингом в границы пространства поиска, чтобы агент не вышел за допустимую область.

Затухание инерции. В конце итерации a1Cur уменьшается в wDamp раз. В статье это прямо не описано, но является стандартным приёмом PSO-семейства, способствующим плавному переходу от разведки к эксплуатации по мере выполнения бюджета. При wDamp = 0.99 и 200 итерациях инерция падает с 0.75 до приблизительно 0.10, что соответствует ожидаемому поведению алгоритма: широкое исследование в начале, уточнение решений в конце.

//+------------------------------------------------------------------+
void C_AO_BUZOA::Revision()
 {
//--- 1. Обновление личных и глобального лучших
  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);
     }
   }

//--- 2. Обновление способности и позиции
  for(int i = 0; i < popSize; i++)
   {
    // Режим охоты: одно значение на агента, одинаково для всех координат
    double rMode = u.RNDfromCI(0.0, 1.0);

    for(int c = 0; c < coords; c++)
     {
      double pos   = a [i].cP [c];    // текущая позиция L(t-1)
      double Ci    = C [Idx(i, c)];   // текущая способность C(t-1)
      double pBest = a [i].cB [c];    // личный рекорд
      double gBest = cB [c];          // глобальный рекорд
      double CNew;

      if(rMode < 0.333)
       {
        //--- Режим 001: эксплуатация
        // Грифы охотятся самостоятельно — быстрый, узкий поиск вблизи pBest.
        // C(t) = α₁·C(t-1) + c1·r·(pBest − L)                    ур. (8)
        double r = u.RNDfromCI(0.0, 1.0);
        CNew = a1Cur * Ci + c1 * r * (pBest - pos);
       }
      else
        if(rMode < 0.667)
         {
          //--- Режим 010: исследование через запах
          // Индейский гриф ведёт стаю к добыче.
          // C₁ = α₁·C + c1·r·(pBest−L)                             ур. (8)
          // C₂ = C₁ + c1·r₁·(pBest−L) + c2·r₂·(gBest−L)           ур. (9)
          // Все члены — разности позиций, размерно согласованы.
          double r  = u.RNDfromCI(0.0, 1.0);
          double r1 = u.RNDfromCI(0.0, 1.0);
          double r2 = u.RNDfromCI(0.0, 1.0);
          double C1 = a1Cur * Ci + c1 * r  * (pBest - pos);
          CNew = C1
                 + c1 * r1 * (pBest - pos)   // когнитивная: притяжение к pBest
                 + c2 * r2 * (gBest - pos);  // социальная:  притяжение к gBest
         }
        else
         {
          //--- Режим 100: глобальное рассеивание
          // Чёрный гриф замечает добычу или хищника издалека и летит
          // прямо туда — телепортация в случайную точку всего пространства.
          // Способность сбрасывается: агент начинает движение с нуля.
          C [Idx(i, c)] = 0.0;
          a [i].cP [c]  = u.RNDfromCI(rangeMin [c], rangeMax [c]);
          continue; // позиция задана напрямую, стандартное обновление не нужно
         }

      // Ограничение скорости: C ∈ [−Cmax, +Cmax]                 ур. (13)
      CNew = Clamp(CNew, -Cmax [c], Cmax [c]);
      C [Idx(i, c)] = CNew;

      // Обновление позиции: L(t) = L(t-1) + C(t)                  ур. (10)
      a [i].cP [c] = Clamp(pos + CNew, rangeMin [c], rangeMax [c]);
     }
   }

//--- 3. Затухание инерции: α₁ ← α₁ · wDamp
  a1Cur *= wDamp;
 }
//+------------------------------------------------------------------+

Снаружи BUZOA выглядит как стандартный C_AO-алгоритм с типовым жизненным циклом: однократный Init, затем повторяющийся цикл Moving → CalcFunc → Revision. Внутренняя логика при этом чисто PSO-подобная, с дополнением — стохастическим переключателем режимов, который случайным образом подбрасывает агентам либо стандартный PSO-шаг, либо усиленный шаг с двойным когнитивным притяжением, либо полный рестарт. Такая структура обеспечивает одновременно эксплуатацию накопленного опыта и периодическое глобальное исследование пространства поиска без необходимости дополнительных управляющих параметров или сложных механизмов балансировки.


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

Самое информативное в результате — не итоговая цифра, а параметры, при которых она достигнута. Авторская статья рекомендует c₁, c₂ ∈ [1.5, 1.7] и подразумевает wDamp ≈ 0.99. Эмпирический оптимум сместился в c₁ = c₂ = 1.1 (ниже рекомендованного диапазона) и wDamp = 0.67 (на порядок ниже стандартного PSO-значения).

wDamp = 0.67 означает, что инерция испаряется за 5–10 итераций: уже к десятой эпохе α₁ опускается ниже 0.013. То есть алгоритм работает лучше всего тогда, когда его собственная динамика быстро отключается, а движение сводится к чистому притяжению к p* и g*. Режимная архитектура BUZOA даёт алгоритму возможность работать хорошо, но не за счёт своих режимов, а вопреки части собственной кинематики. 

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

BUZOA|Buzzard Optimization Algorithm|50.0|0.75|1.1|1.1|0.67|
=============================
5 Hilly's; Func runs: 10000; result: 0.815572465271098
25 Hilly's; Func runs: 10000; result: 0.4477550662434088
500 Hilly's; Func runs: 10000; result: 0.25901610272316844
=============================
5 Forest's; Func runs: 10000; result: 0.7121652523799167
25 Forest's; Func runs: 10000; result: 0.2537132619669822
500 Forest's; Func runs: 10000; result: 0.06530761795980346
=============================
5 Megacity's; Func runs: 10000; result: 0.6066666666666667
25 Megacity's; Func runs: 10000; result: 0.23253333333333331
500 Megacity's; Func runs: 10000; result: 0.10430666666666759
=============================
All score: 3.49704 (38.86%)

На малоразмерных версиях тестов (5 Hilly, 5 Forest, 5 Megacity) BUZOA показывает достойные 60–80%, что для пяти координат и бюджета 10 000 вычислений — нормальный результат для PSO-семейства, честный алгоритм.

Composite test: Hilly + Forest + Megacity + Peaks + Skin
Coordinates: 10; Epochs: 200; Repeats: 10
=============================
Run 1/10: 0.716178040564617
Run 2/10: 0.7331725752886197
Run 3/10: 0.7153614864917345
Run 4/10: 0.7153614864917345
Run 5/10: 0.7931022628996451
Run 6/10: 0.7931022628996451
Run 7/10: 0.7902473358804536
Run 8/10: 0.7098239581426679
Run 9/10: 0.7098239581426679
Run 10/10: 0.8013033806407709
=============================
Average result: 0.7477476747 (74.77%)
=============================

Визуализация работы алгоритма BUZOA на тестовых функциях, а также на других, стандартных.

Hilly

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

Forest

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

Megacity

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

Ackley

BUZOA на функции Ackley

Skin

BUZOA на функции Skin

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

cc
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 1,00000 0,88228 0,40138 2,28366 1,00000 0,95281 0,28092 2,23373 0,94667 0,85733 0,22389 2,02789 6,545 72,72
2 AMOm animal migration optimization M 0,91624 0,83603 0,46790 2,22017 0,98482 0,92010 0,36391 2,26883 0,91733 0,81707 0,25177 1,98617 6,475 71,94
3 CLA code lock algorithm (joo) 0,95139 0,86199 0,37879 2,19217 0,99349 0,93500 0,26497 2,19346 0,93600 0,84267 0,24060 2,01927 6,405 71,17
4 (P+O)ES (P+O) evolution strategies 0,86571 0,89539 0,39740 2,15850 0,97761 0,89820 0,26878 2,14459 0,92133 0,80240 0,23952 1,96325 6,266 69,62
5 SDSm stochastic diffusion search M 0,95195 0,84944 0,36249 2,16388 0,98061 0,88457 0,22112 2,08630 0,92267 0,79013 0,21380 1,92660 6,177 68,63
6 AAm archery algorithm M 0,84685 0,73320 0,42590 2,00595 0,96709 0,77837 0,27789 2,02335 0,86133 0,77707 0,28712 1,92552 5,955 66,17
7 SIA simulated isotropic annealing (joo) 0,93543 0,86504 0,38483 2,18530 0,94069 0,80609 0,23835 1,98513 0,86400 0,66160 0,19536 1,72096 5,891 65,46
8 TETA time evolution travel algorithm (joo) 0,91452 0,86369 0,25579 2,03400 0,99654 0,91291 0,14394 2,05339 0,85467 0,82213 0,10443 1,78123 5,869 65,21
9 ESG evolution of social groups (joo) 0,98111 0,79857 0,31167 2,09135 0,98954 0,82270 0,15032 1,96256 0,92133 0,73440 0,15315 1,80888 5,863 65,14
10 CTA comet tail algorithm (joo) 0,92435 0,86786 0,27838 2,07059 0,99039 0,84571 0,19448 2,03058 0,95467 0,69680 0,11008 1,76155 5,863 65,14
11 ECBO enhanced colliding bodies optimization 0,94024 0,72363 0,32356 1,98743 0,99477 0,80291 0,13056 1,92824 0,87600 0,70160 0,17433 1,75193 5,668 62,98
12 DA dialectical algorithm 0,93117 0,75400 0,26205 1,94722 0,98925 0,81375 0,08662 1,88962 0,92667 0,68107 0,11315 1,72089 5,558 61,76
13 BBO biogeography based optimization 0,95876 0,70609 0,35752 2,02237 0,92981 0,70660 0,16970 1,80611 0,87467 0,63013 0,20813 1,71293 5,541 61,57
14 BHAm black hole algorithm M 0,79558 0,76207 0,34682 1,90447 0,99836 0,75798 0,13826 1,89460 0,85067 0,64427 0,17020 1,66514 5,464 60,71
15 HS harmony search 0,91420 0,69049 0,29924 1,90393 0,97627 0,73373 0,14193 1,85193 0,91733 0,62720 0,15364 1,69817 5,454 60,60
16 RFO royal flush optimization (joo) 0,80989 0,74481 0,34546 1,90016 0,95251 0,77926 0,15185 1,88362 0,80400 0,66427 0,19071 1,65898 5,443 60,48
17 BOAm billiards optimization algorithm M 0,76177 0,72421 0,25275 1,73873 0,90890 0,81960 0,28853 2,01703 0,83733 0,74613 0,09763 1,68109 5,437 60,41
18 ASO anarchy society optimization 0,73070 0,73713 0,31195 1,77978 0,99732 0,87700 0,17619 2,05051 0,72000 0,68773 0,18988 1,59761 5,428 60,31
19 EOm extremal optimization_M 0,76527 0,75205 0,31908 1,83640 0,99999 0,76426 0,12437 1,88862 0,84133 0,64133 0,15247 1,63513 5,360 59,56
20 ACS artificial cooperative search 0,75545 0,77162 0,31653 1,84360 1,00000 0,80488 0,10705 1,91193 0,76933 0,60800 0,14157 1,51890 5,274 58,60
21 SSG saplings sowing and growing 0,75436 0,63206 0,35935 1,74577 0,91907 0,69694 0,19755 1,81356 0,81867 0,60533 0,21347 1,63747 5,197 57,74
22 AOSm atomic orbital search M 0,76184 0,68435 0,31344 1,75963 0,90015 0,80044 0,11501 1,81560 0,82800 0,63280 0,15696 1,61776 5,193 57,70
23 TSEA turtle shell evolution algorithm (joo) 0,95809 0,64852 0,29571 1,90232 0,99522 0,58104 0,10542 1,68168 0,92133 0,52160 0,14567 1,58860 5,173 57,48
24 DE differential evolution 0,96398 0,62346 0,26089 1,84833 0,98482 0,77018 0,11459 1,86959 0,93067 0,36213 0,11000 1,40280 5,121 56,90
25 BIO blood inheritance optimization (joo) 0,72580 0,66522 0,31228 1,70330 0,99995 0,68125 0,11540 1,79660 0,85467 0,59333 0,15364 1,60164 5,102 56,69
26 (PO)ES (PO) evolution strategies 0,73972 0,58190 0,38896 1,71058 0,91199 0,59975 0,21262 1,72436 0,82400 0,56240 0,23432 1,62072 5,056 56,18
27 BO bonobo optimizer 0,75555 0,64366 0,32657 1,72578 0,94332 0,70442 0,13999 1,78773 0,73467 0,61440 0,16728 1,51635 5,030 55,89
28 SRA successful restaurateur algorithm (joo) 0,89010 0,63359 0,29115 1,81484 0,96634 0,55285 0,08914 1,60833 0,89333 0,52800 0,13911 1,56044 4,984 55,38
29 CRO chemical reaction optimisation 0,91281 0,65681 0,29866 1,86828 0,90513 0,56020 0,10939 1,57472 0,82800 0,50133 0,14149 1,47082 4,914 54,60
30 BCOm bacterial chemotaxis optimization M 0,82589 0,61733 0,31584 1,75906 0,95296 0,63718 0,11984 1,70998 0,76533 0,51653 0,15800 1,43986 4,909 54,54
31 DOA dream optimization algorithm 0,78522 0,78121 0,36036 1,92679 0,61584 0,42117 0,12254 1,15955 0,86667 0,72587 0,21127 1,80381 4,890 54,33
32 ABO african buffalo optimization 0,92295 0,62528 0,29885 1,84708 0,92992 0,57468 0,09372 1,59832 0,73333 0,51333 0,14324 1,38990 4,835 53,72
33 BSA bird swarm algorithm 0,94432 0,67941 0,26401 1,88774 0,91649 0,65619 0,12054 1,69322 0,80933 0,33547 0,10652 1,25132 4,832 53,69
34 TSm tabu search M 0,87806 0,61040 0,28993 1,77839 0,98116 0,52165 0,08544 1,58825 0,82667 0,49547 0,13552 1,45766 4,824 53,60
35 BSA backtracking search algorithm 0,87128 0,53190 0,28675 1,68993 0,92408 0,51602 0,09153 1,53163 0,96000 0,47253 0,13760 1,57013 4,792 53,24
36 WOAm whale optimization algorithm M 0,93893 0,59477 0,26695 1,80065 0,98036 0,53873 0,07112 1,59021 0,78667 0,47600 0,11892 1,38159 4,772 53,02
37 CSO competitive swarm optimizer 0,85151 0,60786 0,29896 1,75833 0,84085 0,58491 0,11974 1,54550 0,80000 0,48560 0,14184 1,42744 4,731 52,57
38 FBA fractal-based algorithm 0,69419 0,64267 0,28955 1,62641 0,99812 0,54905 0,08705 1,63422 0,76133 0,51253 0,13689 1,41075 4,671 51,90
39 ECOi eco-inspired evolutionary algorithm 0,78817 0,54402 0,29360 1,62579 0,88996 0,46592 0,09747 1,45335 0,78533 0,45173 0,14295 1,38001 4,459 49,54
40 BSO brain storm optimization 0,92207 0,57625 0,29732 1,79564 0,80764 0,42508 0,09448 1,32720 0,77200 0,36533 0,13065 1,26798 4,391 48,79
41 CAm camel algorithm M 0,71534 0,56917 0,35985 1,64436 0,84094 0,47174 0,10850 1,42118 0,70400 0,41947 0,19563 1,31910 4,385 48,72
42 ACOm ant colony optimization_M 0,71885 0,48410 0,30990 1,51285 0,75792 0,48639 0,11871 1,36302 0,83600 0,48667 0,16148 1,48415 4,360 48,44
43 BSO brain storm optimization 0,91331 0,55813 0,29705 1,76849 0,85329 0,44038 0,09447 1,38814 0,72267 0,32880 0,13325 1,18472 4,341 48,23
44 AEFA artificial electric field algorithm 0,88798 0,65769 0,25276 1,79843 0,95550 0,63602 0,03946 1,63098 0,60800 0,16000 0,09845 0,86645 4,296 47,73
45 SOA simple optimization algorithm 0,91664 0,47040 0,27095 1,65799 0,88264 0,31546 0,06044 1,25854 0,82933 0,31733 0,11579 1,26245 4,179 46,43
BUZOA buzzards_optimization_algorithm 0,81557 0,44775 0,25901 1,52233 0,71216 0,25371 0,06530 1,03117 0,60666 0,23253 0,10430 0,94349 3,497 38,86
RW random walk 0,49970 0,32333 0,25791 1,08094 0,30754 0,11470 0,04400 0,46624 0,36133 0,17013 0,10244 0,63390 2,181 24,23


Выводы

Что подтвердилось из биологической метафоры. Несмотря на эти структурные особенности, центральная идея — стохастическое переключение между качественно разными стратегиями — подтвердила свою полезность. Это видно по поведению алгоритма на задачах с большим количеством локальных ловушек: режим 100 (телепортация в случайную точку) действительно спасает агентов из бассейнов притяжения локальных максимумов. 

Метафора «зрение + обоняние + слежение за хищниками» переводится в математику осмысленно: режим 001 = локальный поиск по личному опыту, режим 010 = коллективное движение к лидеру, режим 100 = разрыв пространственной близости. Биологически это разные типы поведения — и алгоритмически они тоже работают по-разному. В этом смысле введение «обещало» не пустую метафору, и обещание выполнено.

Что не подтвердилось. Деградация результатов при росте размерности (с 5 до 500 функций) у BUZOA выражена сильнее, чем у топовых алгоритмов рейтинга. На 500-мерной Forest результат падает до 6.5% — алгоритм фактически теряет способность находить хорошие решения. Это означает, что преимущество от трёхрежимной архитектуры исчерпывается на малых размерностях и не масштабируется. Статья 2019 года тестировала BUZOA на CEC2014 с фиксированной размерностью 30, и в этом режиме алгоритм действительно демонстрирует конкурентоспособные результаты — но за пределами этой ниши его сильные стороны размываются.

Возврат к исходному вопросу. Был ли смысл в новой архитектуре, или это «PSO в перьях»? Ответ: смысл есть, но архитектура реализована не оптимально. Идея случайного переключения режимов с разной балансом exploration/exploitation продуктивна — особенно в виде режима 100, который аналогичен модной сегодня технике «soft restart» в эволюционных стратегиях. Однако конкретное воплощение 2019 года содержит структурные недочёты (двойное когнитивное давление, чрезмерный C_max, противоречивая буквальная запись формулы режима 010, требующая «рациональной интерпретации»), которые мешают алгоритму раскрыть свой потенциал.

Это типичная судьба алгоритмов «второго эшелона» из метаэвристического бума 2010-х: интересная биологическая идея, работоспособная реализация, но недо-инженерия в деталях. Для практического применения BUZOA в задачах оптимизации торговых стратегий он окажется в средней позиции — лучше, чем простой случайный поиск, заметно слабее, чем хорошо настроенные алгоритмы из верхней половины нашего рейтинга. Для теоретического интереса — его трёхрежимная архитектура заслуживает развития: возможные модификации (адаптивная вероятность режима 100, замена телепортации на лёвиевский полёт, симметризация формулы режима 010) могут улучшить результаты, и это направление достойно отдельного исследования.

Итог. BUZOA честно отрабатывает свою биологическую метафору, демонстрирует работоспособность всех заявленных механизмов и подтверждает идею пользы стохастической смены стратегий. Одновременно он показывает, что новая архитектура без аккуратной инженерной доводки даёт скромный результат. Читатель уходит из статьи не только с готовым к использованию кодом, но и с пониманием того, как читать  формулы из академических работ критически: видеть в них не священный текст, а инженерное предложение, требующее проверки и иногда — рациональной интерпретации.

tab

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

chart

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


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

Плюсы:

  1. Решает простые задачи.

Минусы:

  1. Слабые показатели на высоких размерностях задач.

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



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

# Имя Тип Описание
1 #C_AO.mqh
Включаемый файл
Родительский класс популяционных алгоритмов оптимизации
2 #C_AO_enum.mqh
Включаемый файл
Перечисление популяционных алгоритмов оптимизации
3 TestFunctions.mqh
Включаемый файл
Библиотека тестовых функций
4
TestStandFunctions.mqh
Включаемый файл
Библиотека функций тестового стенда
5 TestStand3D.mqh Включаемый файл 3D-панель визуализации для тестового стенда 
6 Utilities.mqh
Включаемый файл
Библиотека вспомогательных функций
7 CalculationTestResults.mqh
Включаемый файл
Скрипт для расчета результатов в сравнительную таблицу
8 Test_AO_All.mq5
Скрипт Единый испытательный стенд для всех популяционных алгоритмов оптимизации
9 Test_AO_AntiCheat Скрипт Тест на читерство алгоритмов оптимизации
10 Simple use of population optimization algorithms.mq5
Скрипт
Простой пример использования популяционных алгоритмов оптимизации без визуализации
11 Test_AO_BUZOA.mq5
Скрипт Испытательный стенд для BUZOA
Прикрепленные файлы |
BUZOA.zip (388.04 KB)
Dynamic Swing Architecture: Распознавание структуры рынка — от свингов до автоматического исполнения сделок Dynamic Swing Architecture: Распознавание структуры рынка — от свингов до автоматического исполнения сделок
В этой статье представлена полностью автоматизированная система на MQL5, предназначенная для точного определения колебаний рынка и торговли ими. В отличие от традиционных индикаторов колебаний с фиксированным баром, эта система динамично адаптируется к меняющейся структуре цен, обнаруживая максимумы и минимумы колебаний в режиме реального времени, чтобы улавливать возможности направления по мере их формирования.
Разработка инструментария для анализа Price Action (Часть 35): Обучение и развертывание прогнозных моделей Разработка инструментария для анализа Price Action (Часть 35): Обучение и развертывание прогнозных моделей
Исторические данные – вовсе не "мусор", а основа любого надежного рыночного анализа. В этой статье мы шаг за шагом пройдем путь от сбора истории до ее использования для обучения прогностической модели, а затем – до развертывания этой модели для прогнозирования цен в реальном времени. Давайте разберемся, как это сделать.
Архитектура машинного обучения в MetaTrader 5 (Часть 6): Проектирование системы кэширования промышленного уровня Архитектура машинного обучения в MetaTrader 5 (Часть 6): Проектирование системы кэширования промышленного уровня
Устали смотреть на индикаторы выполнения вместо того, чтобы тестировать торговые стратегии? Традиционное кэширование не подходит для финансового машинного обучения, что приводит к потере результатов вычислений и вынуждает вас к повторному запуску, что вызывает раздражение. Мы разработали сложную архитектуру кэширования, учитывающую специфику финансовых данных — временные зависимости, сложные структуры данных и постоянную угрозу смещения look-ahead. Наша трёхслойная система обеспечивает значительное повышение скорости, автоматически отбрасывая устаревшие результаты и предотвращая утечку ценных данных. Хватит ждать результатов расчетов — начинайте действовать в темпе, которого требуют рынки.
Нейросети в трейдинге: Поиск устойчивых закономерностей в разнородных рыночных данных (Окончание) Нейросети в трейдинге: Поиск устойчивых закономерностей в разнородных рыночных данных (Окончание)
В статье представлена адаптация фреймворка INFNet в единый вычислительный конвейер для задач анализа финансовых временных рядов. Описана архитектура верхнеуровневого объекта, объединяющего последовательные, контекстные и сценарные потоки данных. Проведено тестирование на исторических данных EURUSD с оценкой устойчивости модели.