Алгоритм андского кондора — Andean Condor Algorithm (ACA)
Содержание
Введение
В предыдущих статьях этой серии за алгоритмами оптимизации сложилась простая иерархия: сверху — методы, которые на стандартных стендах уверенно превышают 60% общего балла; в середине — те, кто держится в диапазоне 45–55% и пригодны для прикладных задач; внизу — алгоритмы, тестировавшиеся авторами на одной-двух простых функциях и разваливающиеся при переходе на сложный или высокоразмерный ландшафт. Поиск перспективных алгоритмов поэтому всегда идёт по двум критериям сразу: насколько алгоритм воспроизводим из опубликованных источников и насколько он способен показать честные числа на жёстком наборе тестов.
Каждый, кто когда-либо подбирал параметры советника, хорошо знает, зачем это нужно. Пространство параметров большое, ландшафт фитнес-функции рваный и многомодальный. Логичный шаг — обратиться к специализированным метаэвристикам, которые умеют балансировать разведку и эксплуатацию пространства решений. Но новых алгоритмов выходит много, и большинство сопровождается уверенными заявлениями об эффективности, выведенными из тестов на одной-двух унимодальных функциях. На сфере отлично выглядят алгоритмы, которые на реальных задачах рассыпаются — поэтому в этой серии статей каждый кандидат проходит проверку на трёх стендах (Hilly, Forest, Megacity) и на трёх размерностях (10, 50 и 1000 переменных).
Сегодняшний кандидат — Andean Condor Algorithm, или ACA, предложенный в 2018 году Boris Almonacid и Ricardo Soto для задачи формирования производственных ячеек, и расширенный самим Almonacid в 2019 году в статье PeerJ для непрерывных доменов. Метафора — паттерн перемещения андского кондора в поисках пищи, который меняется от сезона к сезону: то широкий радиус полёта над предгорьями, то локальные облёты вокруг найденного источника пищи. Это типичная двухфазная структура exploration / intensification, но с любопытным механизмом баланса: performance indicator, опирающийся на среднее значение фитнеса популяции. Кондоры с фитнесом выше среднего переходят к интенсификации, остальные продолжают разведку.
Главная сложность реализации обнаружилась в источниках. Статья автора алгоритма от 2019 года описывает только операторы перемещения, а ключевые детали механизма баланса — что именно делает performance indicator, как из него вычисляется статус каждого кондора, какую роль играют параметры DP и PC — отсылают к статье 2018 года. Поэтому часть пути этой статьи занимает реконструкция: как из аннотации, описаний операторов и общего понимания метаэвристик собрать рабочий вариант ACA. Затем сверить его поведение с теми числами, которые автор всё-таки опубликовал на тестовой задаче.
Самое любопытное открылось при настройке параметра популяции, об этом подробнее ниже. К концу статьи у читателя будет три вещи. Первое — рабочий класс C_AO_ACA в формате нашего фреймворка, готовый к подключению к собственным задачам оптимизации. Второе — честные числа на стандартных стендах с разбором роли параметра популяции. Третье — методологический урок о том, как алгоритм, представленный авторами как swarm intelligence, на практике устроен иначе. Показано, как учитывать это при выборе оптимизатора для проекта.
Реализация алгоритма
Биологический прототип. Андский кондор (Vultur gryphus) — одна из крупнейших летающих птиц планеты, обитающая в высокогорьях Анд от Венесуэлы до Огненной Земли. Его поведение в поиске пищи имеет выраженную сезонную динамику: в благоприятные сезоны радиус полёта от гнезда невелик, кондор облетает уже известные ему места падали и охотится локально; в неблагоприятные периоды область поиска расширяется до сотен километров, и птица регулярно осваивает новые территории. Эта смена режимов между поиском в окрестности и дальней разведкой легла в основу метафоры алгоритма: каждая особь в популяции в каждый момент находится либо в режиме интенсификации (локальная доводка), либо в режиме разведки (широкий обход), а принадлежность к одному из режимов определяется индикатором эффективности — насколько кондор успешен относительно остальной популяции.
Структура агента. Каждый кондор хранит три блока информации: вектор переменных , представляющий точку в пространстве поиска и ограниченный коробкой по каждой координате; статус , указывающий текущий режим (интенсификация или разведка); и приспособленность — значение целевой функции в этой точке.
Инициализация популяции. На старте каждая координата каждого кондора получает равномерно распределённое случайное значение в пределах своих границ:
Стартовые значения приспособленности рассчитываются однократно, статусы при первом запуске можно инициализировать произвольно — на следующей же итерации они будут переназначены.
Performance indicator и присвоение статуса. Это самая интересная часть алгоритма. На каждой итерации популяция сортируется по убыванию приспособленности, и кондорам с верхнего конца ранжированного списка присваивается статус интенсификации, остальным — статус разведки. Граница между группами задаётся параметром (Distribution Parameter) — доля популяции, попадающая в группу интенсификации.
Формально, пусть — ранг агента после сортировки по убыванию (лучший имеет ранг 0), — размер популяции. Тогда:
После этого с вероятностью (Perturbation Coefficient) статус каждого агента инвертируется. Эта стохастическая поправка добавляет небольшой шум к жёсткому ранговому разделению — позволяет иногда худшим агентам перейти в режим доводки, а лучшим — в режим разведки, что помогает алгоритму избежать преждевременной сходимости.
Здесь важная оговорка. В статье 2019 года описание (performance indicator) не приведено напрямую — автор ссылается на статью 2018 года. Изложенная схема ранжирования и порога со стохастической инверсией — это реконструкция, опирающаяся на аннотацию основной статьи (где упоминаются ranking и average fitness) и общую логику метаэвристик группы. Альтернативные интерпретации механизма (например, прямое сравнение каждого агента со средним фитнесом популяции) на тестах дают близкие, но более шумные результаты.
Пример. Пусть популяция из 4 кондоров имеет приспособленности по индексам, , . После сортировки по убыванию ранги получаются: агент 1 (f=7) — ранг 0, агент 3 (f=5) — ранг 1, агент 0 (f=3) — ранг 2, агент 2 (f=1) — ранг 3. Проверяем условие : агенты 1 и 3 (нормированные ранги 0.0 и 0.25) получают статус , агенты 0 и 2 (0.5 и 0.75) — статус . Затем с вероятностью 0.2 у каждого статус может инвертироваться — в среднем половина популяции в каждой итерации работает на интенсификации, половина на разведке, но конкретные кондоры в этих группах меняются.
Оператор разведки (exploration). Применяется к агентам со статусом . У выбранного кондора случайным образом отбирается половина координат — индексы , . Для каждой применяется:
Если развернуть это выражение, получаем , что эквивалентно полной переинициализации этой координаты равномерным распределением во всём допустимом диапазоне. Остальные половины координат остаются без изменений. С точки зрения геометрии — это прыжок кондора в случайную точку гиперплоскости, ортогональной к подпространству оставшихся координат.
Пример. Кондор в 4-мерной задаче с занимает положение . Случайно выбираются координаты 1 и 3. Для координаты 1: , новое значение лежит в . Для координаты 3: , новое значение также в . Координаты 0 и 2 не меняются. Например, при и новое положение кондора: .
Оператор интенсификации (intensification). Применяется к агентам со статусом . Случайно выбирается одна координата и случайно выбирается тип воздействия с равными вероятностями. Изменение производится по правилу:
Это четыре масштаба возмущения в одно действие. Для типичного симметричного домена оператор даёт скачок до (крупный, фактически прыжок в новую часть пространства), — до (средний), — до (точная доводка), — до (микро-доводка). Случайный равновероятный выбор между ними даёт алгоритму встроенный механизм многомасштабного поиска: одна и та же популяция в один и тот же момент может нащупывать удалённые перспективные области через и шлифовать найденное оптимальное значение через .
Пример. Тот же кондор . Выпало , (то есть ): , допустим . Новое значение . Остальные координаты не изменяются. Если бы вместо этого выпало : , допустим , тогда x2=55 — это уже существенный скачок, переносящий кондора в принципиально другую область по этой координате.
Greedy acceptance. После того как все кондоры сгенерировали свои новые положения, целевая функция вычисляется во всех новых точках. Принятие нового положения — жадное: если приспособленность новой позиции не хуже старой, кондор остаётся в новой; иначе откатывается в положение перед итерацией.
Параллельно отдельно поддерживается лучшее найденное за всю историю поиска положение с приспособленностью , которое обновляется, если очередная новая точка превосходит текущий рекорд. Эта пара служит итоговым результатом работы алгоритма.

Рисунок 1. Схема работы алгоритма ACA
Иллюстрация показывает три шага одной итерации:
Step 1. Ранжирование четырёх кондоров по фитнесу с пунктирной линией порога DP=0.5: верхние два (A₁, A₂) — синие, intensification; нижние (A₃, A₄) — оранжевые, exploration. Справа отдельный блок показывает PC-механизм стохастической инверсии статуса.
Step 2a(синяя панель). Вектор координат с подсвеченной случайно выбранной координатой x₃, и четыре масштаба возмущения α/β/γ/ε визуализированы прямоугольниками-полосами, чьи размеры передают разницу в порядках амплитуды: от крупного прыжка ±30 до микро-доводки ±0.05.
Step 2b(оранжевая панель). Вектор с тремя случайно выбранными координатами и шкала диапазона [LB, UB] с точками, показывающими равномерное распределение новых значений.
Step 3. Жадная проверка с двумя выходами: зелёная ветка Keep при и красная Rollback в противном случае, плюс указатель на следующую итерацию.
Псевдокод одной итерации алгоритма выглядит следующим образом:
Вход: популяция кондоров {x_1, ..., x_N} с приспособленностями {f_1, ..., f_N}
параметры DP, PC
границы LB, UB
1. Сохранить снимок текущей популяции (для возможного отката)
2. Ранжирование:
Отсортировать кондоров по убыванию f
r(i) ← ранг агента i
3. Присвоение статуса:
для каждого i = 1..N:
S_i ← T, если r(i)/N < DP, иначе F
с вероятностью PC: S_i ← !S_i
4. Перемещение:
для каждого i = 1..N:
если S_i = T: // intensification
j ← случайная координата из {1, ..., n}
θ ← случайный выбор из {1, 2, 3, 4}
ξ ← сэмпл из распределения α / β / γ / ε по θ
x_{i,j} ← x_{i,j} + ξ
если S_i = F: // exploration
J ← случайные ⌊n/2⌋ координат
для каждой j из J:
δ ← U(LB_j − x_{i,j}, UB_j − x_{i,j})
x_{i,j} ← x_{i,j} + δ
5. Вычислить приспособленности новых позиций
6. Greedy acceptance:
для каждого i = 1..N:
если f(x_new_i) < f(snapshot_i):
откатить x_i в snapshot
7. Обновить глобальный рекорд x_B, f_BЦикл повторяется до достижения заданного лимита вычислений целевой функции (в наших тестах — 10 000).
Алгоритм в целом получается компактным: три параметра (популяция, , ), два оператора перемещения, одна жадная процедура принятия. Состояния агентов между итерациями не несут памяти о прошлых траекториях — нет ни скоростей, как в PSO, ни феромонов, как в ACO, ни общей долгосрочной памяти. Каждый агент в каждой итерации решает локальную задачу: либо мелкой доводкой по одной координате, либо широким сбросом половины координат, исходя из ранга в популяции на этом шаге.
Можем перейти к разбору реализации. Класс C_AO_ACA наследуется от базового класса C_AO нашего фреймворка и подключается к стандартному жизненному циклу метода оптимизации через переопределение двух виртуальных функций — Moving() и Revision(). Снаружи он доступен через три параметра: размер популяции popSize, долю популяции под интенсификацию DP и вероятность стохастической инверсии статуса PC. Значения по умолчанию выставлены под авторскую рекомендацию (popSize = 25, DP = 0.5, PC = 0.2), однако, как будет показано в следующем разделе, эти настройки далеко не оптимальны на сложных тестовых функциях, и реальный пик эффективности достигается при кардинально меньшей популяции.
В конструкторе задаются строковое имя алгоритма (ao_name), его текстовое описание (ao_desc) и ссылка на статью. Помимо этого инициализируется массив params[], через который параметры алгоритма прокидываются во внешний тестовый стенд и его input-параметры. Это стандартная процедура для всех алгоритмов фреймворка.
Метод SetParams() считывает значения параметров из params[] обратно во внутренние поля класса и накладывает предохранители: значения DP и PC ограничены диапазоном [0, 1]. Эта обработка нужна на случай, если внешний тестер передаст некорректные значения.
//+------------------------------------------------------------------+ class C_AO_ACA : public C_AO { public: ~C_AO_ACA() {} C_AO_ACA() { ao_name = "ACA"; ao_desc = "Andean Condor Algorithm"; ao_link = "https://www.mql5.com/ru/articles/22525"; popSize = 25; DP = 0.5; PC = 0.2; ArrayResize(params, 3); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "DP"; params [1].val = DP; params [2].name = "PC"; params [2].val = PC; } void SetParams() { popSize = (int)params [0].val; DP = params [1].val; PC = params [2].val; //--- предохранители if(DP < 0.0) DP = 0.0; if(DP > 1.0) DP = 1.0; if(PC < 0.0) PC = 0.0; if(PC > 1.0) PC = 1.0; } bool Init(const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0); void Moving(); void Revision(); //--- видимые параметры double DP; // доля популяции под intensification (0..1) double PC; // вероятность инверсии статуса (0..1) private: //--- snapshot для greedy acceptance double snap_c []; // [popSize * coords] — плоский буфер double snap_f []; // [popSize] //--- ранжирование и статусы int rankIdx []; // [popSize] bool status []; // [popSize] true = intensification //--- буфер выбора координат для exploration (Fisher–Yates partial) int shuf []; // [coords] }; //+------------------------------------------------------------------+
Метод Init() выполняет одноразовую подготовку алгоритма к работе. Сначала вызывается StandardInit() базового класса — он аллоцирует массив агентов a[], нормирует входные диапазоны и инициализирует служебные поля. Затем заполняется начальная популяция: каждой координате каждого кондора присваивается случайное значение, равномерно распределённое в её допустимом диапазоне [LB_c, UB_c].
Эти значения записываются в массив cP[], а не в c[] — это конвенция фреймворка, по которой cP[] хранит предыдущее положение агента, c[] — текущее, а cB[] — лучшее за всю историю работы агента. На самом первом обращении к Moving() значения cP[] будут перенесены в c[] для оценки целевой функции.
После этого аллоцируются внутренние буферы класса: snap_c (плоский одномерный массив размера popSize × coords для сохранения снимков положений), snap_f (массив фитнесов снимка), rankIdx (перестановка индексов после сортировки), status (булев массив статусов агентов) и shuf (вспомогательный буфер для частичной перетасовки Фишера-Йейтса при выборе координат в exploration).
//+------------------------------------------------------------------+ //| Init | //+------------------------------------------------------------------+ bool C_AO_ACA::Init(const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if(!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) return false; //--- начальная случайная популяция в cP[] (попадёт в c[] на первом Moving) for(int i = 0; i < popSize; i++) for(int c = 0; c < coords; c++) a [i].cP [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); //--- внутренние буферы ArrayResize(snap_c, popSize * coords); ArrayResize(snap_f, popSize); ArrayResize(rankIdx, popSize); ArrayResize(status, popSize); ArrayResize(shuf, coords); return true; } //+------------------------------------------------------------------+
Метод Moving() — рабочая лошадка алгоритма, генерирующая новые положения агентов в каждой итерации. Первый вызов обрабатывается особо: если флаг revision ещё не поднят (то есть это самый старт работы), то c[] заполняется из cP[] через утилиту u.SeInDiSp(), которая попутно дискретизирует значение с учётом заданного шага и подгоняет под границы. Дальше внешний цикл оценит начальную популяцию, и управление вернётся к Revision().
На втором и всех последующих вызовах Moving() выполняется собственно итерация. Прежде всего, текущее положение всех агентов и их фитнесы сохраняются в snap_c и snap_f — этот снимок понадобится позже для greedy-отката. Затем популяция ранжируется по убыванию приспособленности обычной пузырьковой сортировкой (при popSize ≤ 25 этого более чем достаточно по производительности, и нет смысла подключать что-то более изощрённое).
Для каждого агента вычисляется нормированный ранг r/N, и по сравнению его с порогом DP присваивается статус: T для верхней DP-доли популяции, F для остальных. После этого выполняется стохастическая инверсия — с вероятностью PC статус агента переворачивается, что добавляет шум к жёсткому ранговому разделению.
Когда статусы расставлены, начинается генерация новых положений. Агенты со статусом T проходят через ветку интенсификации: случайно выбирается одна координата j из coords, случайно выбирается тип воздействия θ из четырёх вариантов (0..3), и к значению snap_c[i × coords + j] прибавляется соответствующее случайное приращение — выборка из U(0.3·LB, 0.3·UB) для θ=0 (α-оператор), U(0.1·LB, 0.1·UB) для θ=1 (β), U(−1, 1) для θ=2 (γ) или U(−0.05, 0.05) для θ=3 (ε). Остальные координаты агента остаются без изменений.
Агенты со статусом F идут через ветку exploration: половина координат выбирается без повторений через частичный shuffle Фишера-Йейтса (нам не нужно перемешивать весь массив shuf, достаточно прогнать алгоритм для первых ⌊n/2⌋ позиций), и для каждой выбранной значение полностью переинициализируется случайным выбором в её допустимом диапазоне. Результат во всех ветках записывается в a[i].c с попутной дискретизацией через SeInDiSp().
//+------------------------------------------------------------------+ //| Moving | //| | //| Структура итерации: | //| 1. snapshot текущей популяции; | //| 2. ранжирование по фитнесу (по убыванию — maximization); | //| 3. присвоение Status: верхняя DP-доля — intensification, | //| нижняя — exploration; | //| 4. PC-инверсия каждого статуса; | //| 5. для каждого агента — генерация нового кандидата в a[i].c. | //+------------------------------------------------------------------+ void C_AO_ACA::Moving() { //--- первый прогон: cP → c, чтобы внешний цикл оценил FF if(!revision) { 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]); return; } //--- 1. snapshot for(int i = 0; i < popSize; i++) { for(int c = 0; c < coords; c++) snap_c [i * coords + c] = a [i].c [c]; snap_f [i] = a [i].f; } //--- 2. ранжирование (по убыванию f) for(int i = 0; i < popSize; i++) rankIdx [i] = i; for(int i = 0; i < popSize - 1; i++) for(int j = i + 1; j < popSize; j++) if(snap_f [rankIdx [j]] > snap_f [rankIdx [i]]) { int tmp = rankIdx [i]; rankIdx [i] = rankIdx [j]; rankIdx [j] = tmp; } //--- 3-4. status + PC-инверсия for(int k = 0; k < popSize; k++) { int slot = rankIdx [k]; double normRank = (double)k / (double)popSize; status [slot] = (normRank < DP); if(u.RNDfromCI(0.0, 1.0) < PC) status [slot] = !status [slot]; } //--- 5. движение for(int i = 0; i < popSize; i++) { if(status [i]) { //--- INTENSIFICATION: одна координата, один θ ∈ {1..4} int j = (int)u.RNDfromCI(0.0, (double)coords); if(j >= coords) j = coords - 1; if(j < 0) j = 0; int theta = (int)u.RNDfromCI(0.0, 4.0); if(theta > 3) theta = 3; if(theta < 0) theta = 0; double xi = 0.0; double LB = rangeMin [j]; double UB = rangeMax [j]; switch(theta) { case 0: xi = u.RNDfromCI(0.3 * LB, 0.3 * UB); break; // α case 1: xi = u.RNDfromCI(0.1 * LB, 0.1 * UB); break; // β case 2: xi = u.RNDfromCI(-1.0, 1.0); break; // γ default: xi = u.RNDfromCI(-0.05, 0.05); // ε } double v = snap_c [i * coords + j] + xi; a [i].c [j] = u.SeInDiSp(v, rangeMin [j], rangeMax [j], rangeStep [j]); } else { //--- EXPLORATION: половина координат, δ ~ U[LB-x, UB-x] int half = coords / 2; if(half < 1) half = 1; // Fisher–Yates partial shuffle: первые half индексов — случайные без повторов for(int k = 0; k < coords; k++) shuf [k] = k; for(int k = 0; k < half; k++) { int r = k + (int)u.RNDfromCI(0.0, (double)(coords - k)); if(r >= coords) r = coords - 1; if(r < k) r = k; int tmp = shuf [k]; shuf [k] = shuf [r]; shuf [r] = tmp; } for(int kk = 0; kk < half; kk++) { int j = shuf [kk]; double x_old = snap_c [i * coords + j]; double LB = rangeMin [j]; double UB = rangeMax [j]; double delta = u.RNDfromCI(LB - x_old, UB - x_old); double v = x_old + delta; a [i].c [j] = u.SeInDiSp(v, rangeMin [j], rangeMax [j], rangeStep [j]); } } } } //+------------------------------------------------------------------+
Метод Revision() вызывается тестовым стендом после того, как внешний цикл уже вычислил фитнес во всех новых положениях. Логика делится на три части. Первая — обновление личного и глобального рекордов: для каждого агента, если новая приспособленность лучше его лучшей за всю историю (a[i].fB), то текущее положение и фитнес запоминаются как новый личный рекорд; параллельно, если новое значение лучше глобального рекорда (fB класса), обновляется и он вместе с cB. Этот блок выполняется до greedy acceptance потому, что нас интересуют все случаи рекордного значения, даже если новое положение в итоге будет отброшено по жадному правилу.
Вторая часть срабатывает только на первом вызове Revision(): при revision == false устанавливается флаг revision = true, и метод выходит, оставляя начальную популяцию как есть — она уже оценена и зафиксирована. С третьего обращения к методу и далее начинает работать третья часть — собственно greedy acceptance. Для каждого агента сравниваются текущая приспособленность a[i].f с сохранённой snap_f[i]. Если новая хуже снимка — выполняется откат: координаты восстанавливаются из snap_c[i × coords + c], фитнес — из snap_f[i]. Если новая не хуже — изменения принимаются как есть. Так формируется монотонная траектория каждого агента: его приспособленность за всё время работы не убывает.
Несколько технических примечаний об устройстве буферов. Снимок snap_c хранится как плоский одномерный массив длиной popSize × coords с row-major индексацией: snap_c[i × coords + c] — это значение координаты c у агента i. В классах фреймворка чаще используется обёртка вида S_AO_Vector с массивом v[] внутри, но в случае ACA нужен ровно один такой буфер, и заводить ради него отдельную структуру избыточно — плоского массива достаточно, а индексная арифметика умещается в одну строку. Буфер rankIdx[] заполняется в Moving() начальной перестановкой [0, 1, ..., N−1] и переставляется по убыванию snap_f[]. Буфер shuf[] переиспользуется на каждой итерации внутри ветки exploration: он содержит все индексы координат от 0 до n−1, и алгоритм частично перемешивает его — этого хватает, чтобы получить случайные ⌊n/2⌋ индексов без повторений за линейное время.
//+------------------------------------------------------------------+ //| Revision | //| | //| Стандартное обновление fB/cB + greedy acceptance: если новый | //| фитнес хуже снапшота — откат координат и фитнеса агента. | //+------------------------------------------------------------------+ void C_AO_ACA::Revision() { //--- обновление личных и глобального лучших 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); } } if(!revision) { revision = true; return; } //--- greedy acceptance: rollback если стало хуже for(int i = 0; i < popSize; i++) { if(a [i].f < snap_f [i]) { for(int c = 0; c < coords; c++) a [i].c [c] = snap_c [i * coords + c]; a [i].f = snap_f [i]; } } } //+------------------------------------------------------------------+
Результаты тестов
Автор статьи рекомендует размер популяции в 25 кондоров — и при этом значении алгоритм показывает 41,42% общего балла, что заметно ниже порога эффективности около 48%, и выходит за пределы рейтинга.
=============================
5 Hilly's; Func runs: 10000; result: 0.8052284199791254
25 Hilly's; Func runs: 10000; result: 0.4537792998744926
500 Hilly's; Func runs: 10000; result: 0.2666586996118199
=============================
5 Forest's; Func runs: 10000; result: 0.6651158271894628
25 Forest's; Func runs: 10000; result: 0.2311713783192359
500 Forest's; Func runs: 10000; result: 0.052890991104486404
=============================
5 Megacity's; Func runs: 10000; result: 0.8093333333333333
25 Megacity's; Func runs: 10000; result: 0.33359999999999995
500 Megacity's; Func runs: 10000; result: 0.11008000000000095
=============================
All score: 3.72786 (41.42%)
Однако при радикальном сокращении популяции эффективность алгоритма резко возрастает — общий балл доходит до 53.02%, и ACA уверенно входит в таблицу рейтинга. Эта инверсия — материал для отдельного разговора.
ACA|Andean Condor Algorithm|2.0|0.5|0.2|
=============================
5 Hilly's; Func runs: 10000; result: 0.7844401742263607
25 Hilly's; Func runs: 10000; result: 0.5326017349427434
500 Hilly's; Func runs: 10000; result: 0.33108785513596034
=============================
5 Forest's; Func runs: 10000; result: 0.7907177664055441
25 Forest's; Func runs: 10000; result: 0.44960020772191805
500 Forest's; Func runs: 10000; result: 0.10685016826475134
=============================
5 Megacity's; Func runs: 10000; result: 0.9226666666666667
25 Megacity's; Func runs: 10000; result: 0.6773333333333332
500 Megacity's; Func runs: 10000; result: 0.17613600000000043
=============================
All score: 4.77143 (53.02%)
Тест анти-чит показывает сближенную динамику первых показателей функций предыдущего основного теста. Честный алгоритм.
ACA|Andean Condor Algorithm|2.0|0.5|0.2|
=============================
Composite test: Hilly + Forest + Megacity + Peaks + Skin
Coordinates: 10; Epochs: 10000; Repeats: 10
=============================
Run 1/10: 0.8549286580531176
Run 2/10: 0.7878587377860654
Run 3/10: 0.7878587377860654
Run 4/10: 0.7878587377860654
Run 5/10: 0.7878587377860654
Run 6/10: 0.7878587377860654
Run 7/10: 0.7878587377860654
Run 8/10: 0.786583205099778
Run 9/10: 0.786583205099778
Run 10/10: 0.786583205099778
=============================
Average result: 0.7941830700 (79.42%)
=============================
Визуализация работы алгоритма ACA на тестовых функциях, а также на функциях, входящих в поставку, приведена ниже.

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

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

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

ACA на функции Ackley

ACA на функции GoldsteinPrice
По итогам тестирования алгоритм ACA занимает 37-ю позицию в рейтинге.
| 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 | ACA | andean_condor_algorithm | 0,78444 | 0,53260 | 0,33108 | 1,64812 | 0,79071 | 0,44960 | 0,10685 | 1,34716 | 0,92266 | 0,67733 | 0,17613 | 1,77612 | 4,771 | 53,02 |
| 38 | 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 |
| 39 | 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 |
| 40 | 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 |
| 41 | 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 |
| 42 | 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 |
| 43 | 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 |
| 44 | 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 |
| 45 | 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 |
| 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 | |
Выводы
В проведённом исследовании Andean Condor Algorithm показал себя как алгоритм со своим характером и со своими сильными сторонами. При настройке по авторской рекомендации (популяция 25, DP = 0.5, PC = 0.2) общий балл составил 41.42%. Однако при сокращении популяции до двух кондоров — алгоритм поднимается до 53.02% и уверенно входит в топ-45. На расширенном композите из пяти функций при размерности 10 он достигает 79.42%, что снимает подозрение на оверфит под три стандартные функции и подтверждает реальную силу при низкой размерности.
Особо стоит отметить поведение алгоритма на функции Megacity. Это дискретный ступенчатый ландшафт с плоскими плато между уровнями, и многие метаэвристики справляются с такой геометрией с трудом, на плато попросту не за что зацепиться, направление улучшения не считывается. У ACA здесь обнаруживается структурное преимущество. Многомасштабный intensification-оператор с четырьмя категориями возмущения позволяет одновременно искать правильную «плитку» большими α- и β-скачками и шлифовать положение внутри неё мелкими γ- и ε-шагами, а greedy-принятие естественно работает в условиях ступенчатого улучшения целевой функции.
Внутреннее устройство ACA примечательно несколькими особенностями. Во-первых, это компактный алгоритм с минимальным набором параметров — три штуки, две из которых (DP и PC) задают баланс между интенсификацией и разведкой, а третий — размер популяции. Во-вторых, оператор exploration, переинициализирующий половину координат полностью случайным выбором в их полном диапазоне, — нечастый приём среди метаэвристик: большинство алгоритмов разведки делает плавные смещения с убывающим во времени радиусом, а здесь по выбранным осям происходит полный сброс. Это даёт стохастическую комбинацию из локальной доводки и принципиально нелокальных прыжков, причём пропорция между ними регулируется автоматически через ранжирование по фитнесу. В-третьих, отсутствие внутренней памяти между итерациями делает алгоритм лёгким в реализации, дешёвым по памяти и предсказуемым в поведении — каждая итерация локальна по времени и не зависит от долгой предыстории.
Уменьшение популяции улучшает качество работы алгоритма, оптимум приходится на два-четыре агента. Этому есть прозрачное объяснение в рамках фиксированного бюджета вычислений: при 10 000 FF-оценок меньшая популяция означает больше итераций на агента и, соответственно, больше касаний каждой координаты в режиме intensification. Поэтому авторская рекомендация в 25 кондоров оказывается на нашем тестовом наборе не оптимальной.
Важная общая оговорка по всем результатам. Поскольку механизм performance indicator восстанавливался по аннотации основной статьи 2018 года, часть деталей реализации является интерпретацией: точная семантика параметров DP и PC, схема ранжирования, способ вычисления порога. Альтернативные интерпретации этих элементов на тестах дают близкие, но иные по характеру результаты. Это значит, что приведённые числа характеризуют нашу реконструированную реализацию ACA, а не оригинальную авторскую, которая, возможно, ведёт себя в каких-то нюансах иначе.
С практической точки зрения трейдеру имеет смысл рассматривать ACA прежде всего для задач с дискретным или ступенчатым откликом целевой функции. Особенно — при размерности в пределах 20–30 параметров. Здесь алгоритм будет работать предсказуемо, быстро и с минимумом ручной настройки. Для задач с гладким непрерывным ландшафтом большой размерности подбор не настолько естественен, и стоит сравнивать ACA с алгоритмами, рассчитанными на такие условия.
В сухом остатке: ACA — это компактный многомасштабный оптимизатор с двумя нетипичными чертами (полная переинициализация половины координат в exploration и стохастический выбор амплитуды из четырёх категорий в intensification), особенно хорошо работающий на дискретных и ступенчатых функциях невысокой размерности, требующий минимальной настройки и пригодный для соответствующих прикладных задач.

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

Рисунок 3. Гистограмма результатов тестирования алгоритмов (по шкале от 0 до 100, чем больше, тем лучше, где 100 — максимально возможный теоретический результат), в архиве скрипт для расчёта рейтинговой таблицы
Плюсы и минусы алгоритма
Плюсы:
- Небольшое количество внешних параметров.
- Хорошие показатели на дискретной Megacity.
Минусы:
- Склонность к застреванию.
К статье прикреплён архив с актуальными версиями кодов алгоритмов. Автор статьи не несёт ответственности за абсолютную точность в описании канонических алгоритмов, во многие из них были внесены изменения для улучшения поисковых возможностей. Выводы и суждения, представленные в статьях, основываются на результатах проведённых экспериментов.
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 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_ACA.mq5 | Скрипт | Испытательный стенд для ACA |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
MetaTrader 5: конструируйте рынок под стратегию — Renko/Range/Volume, синтетика и стресс-тесты на пользовательских символах
Тестер стратегий для Python и MetaTrader 5 (Часть 05): Тестер стратегий для нескольких символов и таймфреймов
Двумерные копулы в MQL5 (Часть 3): Реализация и настройка смешанных моделей копул
Автоматизация торговых стратегий в MQL5 (Часть 28): Создание гармонического паттерна "Летучая мышь" на основе Price Action с визуальной обратной связью
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования