Алгоритм оптимизации койотов — Coyote Optimization Algorithm (COA)
Содержание
Введение
Каждый, кто хоть раз запускал оптимизацию торговой системы, знает это чувство. Пространство параметров огромно: периоды индикаторов, уровни стопов и тейков, фильтры времени и волатильности — даже у скромного советника легко набирается десяток измерений. Полный перебор невозможен физически. Преждевременная сходимость — главный враг любого, кто ищет устойчивые параметры на многоэкстремальном ландшафте, а ландшафт прибыли торговой системы именно таков: изрезан, зашумлён, полон узких пиков и широких пологих плато, которые нас на самом деле и интересуют.
Есть задача оптимизации, есть понимание, что один рой, сходящийся в одну точку, — это лотерея. Напрашивается вопрос: существуют ли алгоритмы, которые умеют исследовать несколько областей пространства параллельно, не теряя при этом скорости сходимости? В нашей серии статей о популяционных алгоритмах оптимизации мы уже разобрали не один десяток метаэвристик — от классических роевых до самых экзотических — и прогнали каждую через единый испытательный стенд: функции Hilly, Forest и Megacity, три размерности, фиксированный бюджет в 10 000 обращений к целевой функции, десять повторений. Такой единый протокол позволяет не верить авторам алгоритмов на слово, а сравнивать всех в равных условиях. Рейтинговая таблица серии не раз показывала, что громкое название и красивая природная метафора ничего не гарантируют.
Сегодняшний гость серии — Coyote Optimization Algorithm (COA), алгоритм оптимизации койотов, предложенный Жулиано Пьерезаном и Леандро душ Сантушем Коэльо в 2018 году. На первый взгляд — очередной зверь в длинном зоопарке метаэвристик. Но у COA есть структурная особенность: популяция в нём изначально разбита на изолированные стаи, каждая из которых живёт своей социальной жизнью, эволюционирует вокруг собственного лидера и лишь изредка обменивается особями с соседями. Это не рой, который дышит как единое целое, — это архипелаг маленьких популяций, естественным образом удерживающий разнообразие. Плюс два механизма, которых нет в типичном рое: коллективная культура стаи, выраженная через медиану (а не среднее — и это важно), и рождение щенков — генетический кроссовер, встроенный прямо в роевую схему.
К концу статьи у читателя будет: понимание социальной механики COA, готовая реализация класса в рамках стандартной архитектуры C_AO с разбором всех адаптаций последовательной авторской схемы к нашему тестовому стенду, честные результаты на полном наборе функций и место алгоритма в рейтинговой таблице серии. А заодно — ответ на практический вопрос: даёт ли стайная структура реальное преимущество в удержании разнообразия или это лишь красивая обёртка над обычным роем. Сначала разберём идею и математику алгоритма с примерами из жизни, затем перенесём её в MQL5, обсудим подводные камни переноса и проверим результат на стенде.
Реализация алгоритма
Метафора: не рой, а общество
Большинство роевых алгоритмов моделируют толпу: все агенты видят глобального лидера и тянутся к нему. COA моделирует нечто более сложное — общество с социальной структурой.
Койоты в природе живут стаями по несколько особей. В каждой стае есть альфа — самый приспособленный. Есть коллективный опыт стаи — её культура: набор повадок, который не принадлежит ни одной конкретной особи, но разделяется всеми. Койоты учатся друг у друга, в стаях рождаются щенки, наследующие черты родителей, а иногда особь покидает стаю и присоединяется к другой, перенося с собой накопленный опыт.
Чтобы перевести это на язык трейдера, представьте не одну команду разработчиков торговых систем, а несколько независимых команд. У каждой команды свой лидер с лучшим текущим результатом и своя внутренняя школа — типичный для команды стиль торговли. Внутри команды идёт постоянный обмен идеями, время от времени появляются гибридные стратегии, собранные из кусков двух чужих, а изредка трейдер уходит из одной команды в другую — и приносит туда свежий взгляд. Несколько команд почти наверняка исследуют разные торговые идеи; одна большая команда почти наверняка скатится к одной. Теперь по механизмам.
Койот = решение, стая = субпопуляция. Каждый койот — это вектор параметров задачи, то есть кандидат-решение: в наших терминах — конкретный набор параметров торговой системы. Социальное состояние койота — его координаты в пространстве поиска, приспособленность — значение целевой функции.
Популяция из popSize койотов разбивается на nPacks стай по nCoy особей в каждой (авторы используют 20 стай по 5 койотов). Разбиение случайно и — ключевой момент — почти постоянно: стаи не пересортировываются каждую итерацию по качеству, как это делают некоторые алгоритмы с кастами. Состав стаи меняется лишь редким событием обмена, о котором ниже. Именно стабильная изоляция и позволяет стаям разойтись по разным областям ландшафта.
Альфа и культура стаи. На каждой итерации в каждой стае определяются две опорные точки. Первая — альфа: лучший койот стаи по приспособленности. Это аналог локального лидера — не глобального! Каждая стая равняется на своего чемпиона, а не на чемпиона мира. Если одна стая нашла высокий пик, остальные не бросают свои области и продолжают копать там, где стоят. Вторая — социальная тенденция стаи: покоординатная медиана положений всех её койотов. Это и есть формализация культуры — обобщённый портрет стаи, не совпадающий ни с одной конкретной особью.
Почему медиана, а не среднее — деталь, которую стоит оценить. Представьте команду из пяти трейдеров, четверо из которых торгуют с периодом скользящей средней около 20, а пятый — экспериментатор — с периодом 200. Среднее арифметическое даст 56 — значение, которым не торгует никто и которое не отражает ни консенсус, ни эксперимент. Медиана даст 20 — честный портрет того, "как принято в этой команде". Медиана устойчива к выбросам: один забредший на край пространства койот не утаскивает за собой культуру всей стаи. В алгоритме, где выбросы — штатный результат разведки, это осознанное инженерное решение.
Социальный ход: учиться у лидера и у среды. Основной оператор движения. Каждый койот стаи (кроме одного — его место займёт щенок, об этом ниже) строит новое социальное состояние по формуле:
new = x + r1 * (alpha - x_rc1) + r2 * (tend - x_rc2)
где:
- x — текущее положение койота,
- alpha — позиция альфы,
- tend — культурная тенденция стаи,
- x_rc1 и x_rc2 — два случайно выбранных сородича из той же стаи (различных и не совпадающих с самим койотом),
- r1, r2 — случайные числа из [0; 1].
Разберём, что здесь происходит содержательно. Койот делает два шага одновременно. Первый — чему меня учит лидер: вектор (alpha - x_rc1) показывает, насколько и в какую сторону альфа отличается от случайного рядового члена стаи. Второй — чему меня учит среда: вектор (tend - x_rc2) показывает, насколько случайный сородич отклоняется от культурной нормы, и тянет в сторону этой нормы.
Житейский пример: вы — трейдер в команде. Вы смотрите на лидера и на случайного коллегу и думаете: "у лидера стоп в два раза короче, чем у Пети, — попробую и я укоротить". Затем смотрите на общекомандную практику и на другого коллегу: "у нас в команде принято фильтровать азиатскую сессию, а Вася не фильтрует — пожалуй, я ближе к командной практике". Важно, что точкой отсчёта оба раза служит не сам обучающийся, а случайные сородичи. Это вносит в шаг стохастику, зависящую от текущего разброса стаи, — пока стая широкая, шаги размашистые, когда стая сжалась вокруг пика, шаги автоматически мельчают. Встроенный, простой механизм перехода от разведки к уточнению, без всяких затухающих коэффициентов от номера итерации.
Принятие нового состояния — жадное и строгое: койот переходит в новую точку только если она лучше прежней. Никаких ухудшений ради разнообразия — за разнообразие в COA отвечают другие механизмы.
Рождение щенка: генетика внутри роя
Самая необычная часть алгоритма. Раз в итерацию в каждой стае рождается щенок — новое решение, собранное из генов двух случайных родителей-сородичей плюс свежий случайный материал.
Работает это так. Случайным образом выбираются два различных родителя. Затем для каждой координаты решается её судьба: достаться от первого родителя, от второго или быть разыгранной заново случайно (мутация). При этом алгоритм гарантирует, что хотя бы одна координата возьмётся от первого родителя и хотя бы одна — от второго: щенок не может оказаться чистым клоном или чистой случайностью. Вероятность мутации по координате равна Ps = 1/D, где D — размерность задачи, то есть в среднем мутирует одна координата на щенка независимо от размерности; остальные координаты делятся между родителями поровну, с вероятностью (1 - Ps)/2 каждая.
На языке торговых систем: берём систему Маши и систему Игоря, у новой системы период канала — от Маши, коэффициент тейка — от Игоря, фильтр волатильности — от Маши, а время торговой сессии разыгрываем заново, наугад. Получается гибрид, которого не было ни у кого, — и иногда такой гибрид выстреливает там, где оба родителя были посредственны, потому что удачная комбинация параметров не обязана лежать на отрезке между двумя удачными решениями. Социальный ход — оператор непрерывный, он умеет двигаться по направлениям; кроссовер — оператор комбинаторный, он умеет перепрыгивать в недостижимые для плавного движения углы пространства. В COA они работают в паре, и это редкое для роевых алгоритмов сочетание.
Судьба щенка решается естественным отбором: он занимает место худшего койота стаи — если оказывается лучше него. Здесь в игру вступает возраст: при прочих равных вытесняется самый старый из худших. Возраст всех койотов растёт на единицу каждую итерацию, новорождённый щенок начинает с нуля. Стая медленно, но непрерывно омолаживается, и застрявшие ветераны не могут вечно занимать место только за выслугу лет.
Обмен между стаями: редкий, но важный. Если бы стаи были изолированы полностью, алгоритм выродился бы в набор независимых маленьких оптимизаций — каждая со своим бюджетом и своими тупиками. Авторы добавляют тонкий канал связи: с вероятностью p_leave = 0.005 * nCoy² за итерацию два случайных койота из двух случайных стай меняются местами.
При стандартных 5 койотах на стаю это 0.125 — обмен происходит примерно раз в восемь итераций. Достаточно редко, чтобы стаи сохраняли индивидуальность, и достаточно часто, чтобы находка одной стаи рано или поздно эмигрировала к соседям. Это та самая модель архипелага из теории островных генетических алгоритмов: изоляция удерживает разнообразие, миграция распространяет успех. Обратите внимание на квадратичную зависимость от размера стаи — авторы калибровали частоту обмена так, чтобы крупные стаи, которым внутреннего разнообразия хватает с избытком, обменивались чаще и не закисали.
Собираем картину целиком
Один "год" жизни популяции выглядит так. В каждой стае: определили альфу, вычислили культурную медиану, каждый койот сделал социальный ход с жадным принятием, родился и поборолся за место щенок. Между стаями: возможно, случился обмен парой особей. Все постарели на год. Лучшее решение среди всех стай — текущий ответ алгоритма.
В этой схеме хорошо видно разделение труда между механизмами: социальный ход отвечает за локальное уточнение и сходимость, кроссовер щенка — за комбинаторные скачки и крупные открытия, стайная структура — за параллельное удержание нескольких областей поиска, обмен — за распространение успеха, возраст — за ротацию состава. Ни один из операторов не дублирует другой — для алгоритма 2018 года, придуманного в эпоху, когда новые метаэвристики штамповались переименованием старых, это приятная редкость.
Насколько хорошо эта конструкция работает на деле — покажет стенд. А пока перейдём к реализации на MQL5 и к тем местам, где последовательную авторскую схему пришлось аккуратно адаптировать к нашей архитектуре C_AO.

Рисунок 1. Алгоритм оптимизации койотов: социальная структура поиска
На иллюстрации популяция разделена на изолированные стаи, каждая из которых занимает собственный регион мультимодального ландшафта. Каждая стая следует своему собственному альфе (лучшему койоту стаи), а не единому глобальному лидеру, что естественным образом сохраняет разнообразие. Социальная тенденция стаи — это покоординатная медиана её членов; она устойчива к выбросам, в отличие от среднего значения. Редкая миграция (вероятность p_leave = 0.005·nCoy² на итерацию, примерно один раз в 8 итераций при nCoy = 5) меняет местами двух койотов между двумя случайными стаями, распространяя успешные признаки по всему архипелагу.
-
Социальное движение: каждый койот одновременно делает шаг в сторону альфы и в сторону культуры стаи, используя двух случайных соплеменников (rc1 и rc2) как точки отсчёта; новая позиция принимается только если она строго улучшает решение.
-
Рождение детёныша: один раз за итерацию каждая стая производит детёныша с помощью бинарного кроссовера двух случайных родителей — как минимум один ген гарантированно наследуется от каждого родителя, а каждый ген мутирует в новое случайное значение с вероятностью Ps = 1/D.
-
Возраст и замещение: каждый койот стареет на единицу за итерацию; детёныш заменяет худшего койота стаи (самого старого при равенстве приспособленности) только если он лучше, так что стая постоянно омолаживается, не теряя качества.
Псевдокод алгоритма COA:
popSize, nCoy, nPacks = popSize/nCoy, coords — размеры pLeave = 0.005 * nCoy^2 — вероятность обмена стай (Eq. 4) Ps = 1/coords — вероятность мутации координаты щенка probPup = (1 - Ps)/2 — вероятность наследования координаты x[i], f[i] — координаты и фитнес койота snap_c[i], snap_f[i] — точка на начало эпохи age[i], isPup[i] — возраст и флаг щенка pack[p] — nCoy индексов койотов стаи p cB, fB — глобально лучшие координаты и фитнес INIT StandardInit(rangeMin, rangeMax, rangeStep) pLeave = 0.005 * nCoy^2; probPup = (1 - 1/coords)/2 for i, c: cP[i][c] = random_uniform(rangeMin[c], rangeMax[c]) # стартовая популяция age[*] = 0; isPup[*] = false indices = shuffle([0 .. popSize-1]) # Fisher–Yates распределить indices по pack[0..nPacks-1], по nCoy в каждой MOVING if первый_прогон: # только cP -> c, чтобы стенд оценил FF for i, c: x[i][c] = bring_into_range(cP[i][c]) return for i: snap_c[i] = x[i]; snap_f[i] = f[i] # 1. точка отсчёта эпохи if nPacks > 1 and random() < pLeave: # 2. редкий обмен между стаями (Eq. 4) обменять pack[p1][c1] <-> pack[p2][c2] для случайных p1!=p2, c1, c2 isPup[*] = false for p = 0 .. nPacks-1: # 3. операции внутри стаи alpha = argmax_{k in pack[p]} snap_f[k] # альфа стаи (Eq. 5) for c: tend[c] = median_{k in pack[p]} snap_c[k][c] # тенденция = медиана (Eq. 6) pup_slot = худший по snap_f (при равенстве — старший по age) # слот щенка isPup[pup_slot] = true for каждого i из pack[p]: if i == pup_slot: # РОЖДЕНИЕ ЩЕНКА (Eq. 7, Alg. 1) выбрать двух разных родителей par1, par2 из стаи pdr = shuffle([0 .. coords-1]) mask[*] = 0; mask[pdr[0]] = 1; mask[pdr[1]] = 2 # минимум по 1 от каждого for j = 2 .. coords-1: r = random() if r < probPup: mask[pdr[j]] = 1 elif r > 1 - probPup: mask[pdr[j]] = 2 # иначе 0 = мутация for c: v = (mask[c]==1) ? snap_c[par1][c] : (mask[c]==2) ? snap_c[par2][c] : random_uniform(rangeMin[c], rangeMax[c]) x[i][c] = bring_into_range(v) else: # СОЦИАЛЬНЫЙ ХОД (Eq. 12) выбрать rc1, rc2 из стаи (оба != i, rc1 != rc2) r1 = random(); r2 = random() # скаляры на агента for c: v = snap_c[i][c] + r1*(snap_c[alpha][c] - snap_c[rc1][c]) # тяга к лидеру + r2*(tend[c] - snap_c[rc2][c]) # тяга к культуре стаи x[i][c] = bring_into_range(v) После Moving стенд вычисляет f[i] для всех агентов — ровно popSize вызовов FF. REVISION for i: # обновление лучших обновить личный лучший, если f[i] выше if f[i] > fB: fB = f[i]; cB = x[i] if первый_прогон: revision = true; return for i: # адаптация (Eq. 14): greedy-приёмка accepted = (f[i] > snap_f[i]) if not accepted: x[i] = snap_c[i]; f[i] = snap_f[i] # откат к точке отсчета age[i]++ if isPup[i] and accepted: age[i] = 0 # щенок обнуляет возраст ВНЕШНИЙ ЦИКЛ (стенд) Init(); Moving(); оценить f[*]; Revision() for epoch = 1 .. epochCount: Moving(); оценить f[*]; Revision() ответ = cB (фитнес fB)
Теперь реализация. Класс C_AO_COA_Coyote наследует базовый C_AO и реализует Coyote Optimization Algorithm в рамках стандартной архитектуры серии: вся логика итерации распределена между двумя методами Moving (формирование новых кандидатов) и Revision (оценка принятия и обновление лучших), которые внешний цикл стенда вызывает по очереди на каждой эпохе. Базовый класс не модифицируется.
Видимый параметр nCoy задаёт число койотов в стае и вместе с унаследованным popSize определяет служебное nPacks = popSize / nCoy — количество стай. Параметры pLeave (вероятность обмена особями между стаями, равная 0.005·nCoy²) и probPup (вероятность наследования координаты щенком, равная (1−1/coords)/2) вычисляются один раз в Init , поскольку зависят только от nCoy и размерности.
Состав стай хранится в плоском массиве packs длиной nPacks·nCoy: индексы койотов, принадлежащих стае p, лежат в срезе packs[p·nCoy .. p·nCoy+nCoy−1]. Массив ages хранит возраст каждого койота, isPup помечает слот, занятый щенком в текущую эпоху.
Снимок состояния популяции на начало эпохи держат snap_c (плоский буфер координат, popSize·coords) и snap_f (фитнесы, popSize) — от него считаются все ходы эпохи и к нему откатываются непринятые кандидаты. Рабочие буферы medBuf (для вычисления медианы стаи), tend (социальная тенденция), pdr (перестановка измерений при рождении щенка) и mask (источник каждой координаты щенка) переиспользуются и не несут состояния между эпохами.
Конструктор задаёт идентификацию алгоритма (ao_name, ao_desc, ссылку на статью) и значения по умолчанию: popSize = 20, nCoy = 5. Регистрирует два настраиваемых параметра в массиве params и сразу вычисляет nPacks, чтобы объект был консистентен ещё до явного вызова SetParams.
SetParams cчитывает popSize и nCoy из массива params (куда их кладёт внешний скрипт перед запуском) и пересчитывает nPacks = popSize / nCoy. popSize намеренно не корректируется: это внешний параметр в зоне ответственности пользователя, который обязан задать его кратным nCoy.
//+------------------------------------------------------------------+ //| Class | //+------------------------------------------------------------------+ class C_AO_COA_Coyote : public C_AO { public: ~C_AO_COA_Coyote() {} C_AO_COA_Coyote() { ao_name = "COA(Coyote)"; ao_desc = "Coyote Optimization Algorithm"; ao_link = "https://www.mql5.com/ru/articles/23053"; popSize = 20; nCoy = 5; ArrayResize(params, 2); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "nCoy"; params [1].val = nCoy; nPacks = popSize / nCoy; } void SetParams() { popSize = (int)params [0].val; nCoy = (int)params [1].val; nPacks = popSize / nCoy; } bool Init(const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0); void Moving(); void Revision(); //--- видимые параметры int nCoy; // койотов в стае (оригинал: 5) private: int nPacks; // число стай = popSize / nCoy double pLeave; // вероятность обмена между стаями: 0.005*nCoy^2 double probPup; // (1 - Ps)/2, Ps = 1/coords int packs []; // [nPacks * nCoy] — индексы агентов по стаям int ages []; // [popSize] — возраст койота bool isPup []; // [popSize] — слот щенка в текущую эпоху //--- snapshot для greedy acceptance double snap_c []; // [popSize * coords] double snap_f []; // [popSize] //--- рабочие буферы double medBuf []; // [nCoy] — для медианы double tend []; // [coords] — социальная тенденция стаи int pdr []; // [coords] — перестановка измерений (щенок) int mask []; // [coords] — 0: шум, 1: родитель 1, 2: родитель 2 };
Инициализация подготавливает алгоритм к новому прогону. Сначала вызывает StandardInit базового класса (он выделяет популяцию a[] и сохраняет границы поиска), затем заполняет стартовую популяцию случайными координатами в буфере предложений cP[] — на первом Moving они будут перенесены в рабочие координаты c[]. Здесь же вычисляются pLeave и probPup, выделяются все служебные буферы, обнуляются возрасты и флаги щенков. Завершается метод случайным разбиением популяции по стаям: индексы 0..popSize−1 перемешиваются алгоритмом Фишера—Йетса и раскладываются по packs.
//+------------------------------------------------------------------+ //| Init | //+------------------------------------------------------------------+ bool C_AO_COA_Coyote::Init(const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if(!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) return false; //--- начальная случайная популяция в cP[] (Eq. 2) for(int i = 0; i < popSize; i++) for(int c = 0; c < coords; c++) a [i].cP [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); //--- параметры, зависящие от nCoy и coords pLeave = 0.005 * (double)nCoy * (double)nCoy; // Eq. 4 probPup = (coords > 0) ? (1.0 - 1.0 / (double)coords) * 0.5 : 0.0; // (1-Ps)/2 //--- буферы ArrayResize(snap_c, popSize * coords); ArrayResize(snap_f, popSize); ArrayResize(packs, nPacks * nCoy); ArrayResize(ages, popSize); ArrayResize(isPup, popSize); ArrayResize(medBuf, nCoy); ArrayResize(tend, coords); ArrayResize(pdr, coords); ArrayResize(mask, coords); ArrayInitialize(ages, 0); ArrayInitialize(isPup, false); //--- случайное распределение койотов по стаям (Fisher–Yates) for(int i = 0; i < popSize; i++) packs [i] = i; for(int j = popSize - 1; j > 0; j--) { int r = u.RNDminusOne(j + 1); int tmp = packs [j]; packs [j] = packs [r]; packs [r] = tmp; } return true; }
Moving формирует новых кандидатов для всех агентов за одну эпоху, не обращаясь к целевой функции (её считает внешний цикл после возврата из метода). На первом прогоне (revision == false) метод лишь переносит стартовую популяцию из cP[] в рабочие координаты c[] через приведение к сетке и границам, после чего выходит — это даёт стенду возможность оценить исходную популяцию.
На рабочих эпохах метод последовательно: снимает снапшот текущей популяции в snap_c / snap_f; с вероятностью pLeave обменивает по одному случайному койоту между двумя случайными стаями (миграция, Eq. 4); сбрасывает флаги щенков. Затем для каждой стаи определяет альфу (койота с максимальным снапшот-фитнесом, Eq. 5), вычисляет социальную тенденцию как покоординатную медиану членов стаи (Eq. 6) и назначает слот щенка — худшего по фитнесу койота, а при равенстве самого старшего по возрасту.
После этого каждый койот стаи получает нового кандидата. Койот в слоте щенка рождается заново (Eq. 7, Alg. 1): выбираются два различных родителя, измерения перемешиваются, и по маске каждая координата берётся от первого родителя, от второго или разыгрывается случайно (мутация с вероятностью 1/coords), причём минимум по одной координате гарантированно достаётся каждому родителю. Остальные койоты делают социальный ход (Eq. 12): к снапшот-позиции добавляются тяга к альфе и тяга к тенденции стаи, обе масштабированные скалярами r1 и r2, разыгранными на агента. Все новые координаты приводятся к границам и сетке поиска.
//+------------------------------------------------------------------+ //| Moving | //| | //| Структура эпохи: | //| 1. snapshot популяции; | //| 2. редкий обмен койотами между стаями (Eq. 4); | //| 3. для каждой стаи: альфа, тенденция-медиана, слот щенка; | //| 4. социальные ходы (Eq. 12) для всех, кроме слота щенка; | //| 5. в слот щенка — кроссовер двух родителей + шум (Alg. 1). | //+------------------------------------------------------------------+ void C_AO_COA_Coyote::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; } //--- 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; } //--- обмен койотами между стаями (Eq. 4) if(nPacks > 1 && u.RNDprobab() < pLeave) { int p1 = u.RNDminusOne(nPacks); int p2 = p1; while(p2 == p1) p2 = u.RNDminusOne(nPacks); int c1 = u.RNDminusOne(nCoy); int c2 = u.RNDminusOne(nCoy); int tmp = packs [p1 * nCoy + c1]; packs [p1 * nCoy + c1] = packs [p2 * nCoy + c2]; packs [p2 * nCoy + c2] = tmp; } ArrayInitialize(isPup, false); //--- операции внутри каждой стаи for(int p = 0; p < nPacks; p++) { int base = p * nCoy; //--- альфа стаи (Eq. 5): максимум фитнеса (стенд максимизирует) int aIdx = packs [base]; for(int k = 1; k < nCoy; k++) { int idx = packs [base + k]; if(snap_f [idx] > snap_f [aIdx]) aIdx = idx; } //--- социальная тенденция (Eq. 6): покоординатная медиана стаи for(int c = 0; c < coords; c++) { for(int k = 0; k < nCoy; k++) medBuf [k] = snap_c [packs [base + k] * coords + c]; ArraySort(medBuf); tend [c] = (nCoy % 2 == 1) ? medBuf [nCoy / 2] : 0.5 * (medBuf [nCoy / 2 - 1] + medBuf [nCoy / 2]); } //--- слот щенка: худший фитнес, при равенстве — старший возраст int wK = 0; for(int k = 1; k < nCoy; k++) { int idx = packs [base + k]; int wIdx = packs [base + wK]; if(snap_f [idx] < snap_f [wIdx] || (snap_f [idx] == snap_f [wIdx] && ages [idx] > ages [wIdx])) wK = k; } int pupIdx = packs [base + wK]; isPup [pupIdx] = true; //--- движение койотов стаи for(int k = 0; k < nCoy; k++) { int i = packs [base + k]; if(i == pupIdx) { //--- РОЖДЕНИЕ ЩЕНКА (Eq. 7, Alg. 1) // два случайных различных родителя из стаи int k1 = u.RNDminusOne(nCoy); int k2 = k1; while(k2 == k1) k2 = u.RNDminusOne(nCoy); int par1 = packs [base + k1]; int par2 = packs [base + k2]; //--- перестановка измерений (Fisher–Yates) for(int c = 0; c < coords; c++) pdr [c] = c; for(int j = coords - 1; j > 0; j--) { int r = u.RNDminusOne(j + 1); int tmp = pdr [j]; pdr [j] = pdr [r]; pdr [r] = tmp; } //--- маска: гарантированно по одной координате от каждого // родителя, остальные — родитель 1 / родитель 2 / шум ArrayInitialize(mask, 0); mask [pdr [0]] = 1; if(coords > 1) mask [pdr [1]] = 2; for(int j = 2; j < coords; j++) { double r = u.RNDprobab(); if(r < probPup) mask [pdr [j]] = 1; else if(r > 1.0 - probPup) mask [pdr [j]] = 2; // иначе 0 — равномерный шум по координате } for(int c = 0; c < coords; c++) { double v; if(mask [c] == 1) v = snap_c [par1 * coords + c]; else if(mask [c] == 2) v = snap_c [par2 * coords + c]; else v = u.RNDfromCI(rangeMin [c], rangeMax [c]); a [i].c [c] = u.SeInDiSp(v, rangeMin [c], rangeMax [c], rangeStep [c]); } } else { //--- СОЦИАЛЬНЫЙ ХОД (Eq. 12) // rc1, rc2 — различные, не совпадающие с k int k1 = k; while(k1 == k) k1 = u.RNDminusOne(nCoy); int k2 = k; while(k2 == k || k2 == k1) k2 = u.RNDminusOne(nCoy); int rc1 = packs [base + k1]; int rc2 = packs [base + k2]; //--- r1, r2 — скаляры на агента, как в оригинале double r1 = u.RNDprobab(); double r2 = u.RNDprobab(); for(int c = 0; c < coords; c++) { double v = snap_c [i * coords + c] + r1 * (snap_c [aIdx * coords + c] - snap_c [rc1 * coords + c]) + r2 * (tend [c] - snap_c [rc2 * coords + c]); a [i].c [c] = u.SeInDiSp(v, rangeMin [c], rangeMax [c], rangeStep [c]); } } } // k } // p }
Revision вызывается после того, как внешний цикл вычислил фитнес всех новых кандидатов. Сначала метод обновляет личные лучшие результаты агентов и глобальный лучший (fB, cB). На первом прогоне он лишь выставляет флаг revision и выходит — рабочей популяции для сравнения ещё нет.
На рабочих эпохах выполняется адаптация (Eq. 14): кандидат принимается только при строгом улучшении фитнеса относительно снапшота, иначе координаты и фитнес агента откатываются (greedy-acceptance — касается и социальных ходов, и щенков). В завершение возраст всех койотов увеличивается на единицу, а у прижившегося щенка обнуляется — так стая непрерывно ротируется, не позволяя застрявшим ветеранам бесконечно занимать слот.
//+------------------------------------------------------------------+ //| Revision | //| | //| Стандартное обновление fB/cB + адаптация (Eq. 14): кандидат | //| принимается только при СТРОГОМ улучшении, иначе откат к | //| снапшоту (касается и социальных ходов, и щенка). Возраст всех | //| койотов растёт на 1 за эпоху; прижившийся щенок обнуляет | //| возраст слота. | //+------------------------------------------------------------------+ void C_AO_COA_Coyote::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; } //--- адаптация (Eq. 14): принятие только при строгом улучшении for(int i = 0; i < popSize; i++) { bool accepted = (a [i].f > snap_f [i]); if(!accepted) { for(int c = 0; c < coords; c++) a [i].c [c] = snap_c [i * coords + c]; a [i].f = snap_f [i]; } //--- возраст ages [i]++; if(isPup [i] && accepted) ages [i] = 0; } }
Внешний цикл стенда повторяет связку Moving → расчёт фитнеса → Revision. Moving готовит кандидатов от единого снапшота, стенд оценивает их ровно popSize вызовами целевой функции, Revision решает их судьбу и обновляет лучшие. Разделение на два метода — требование архитектуры серии: оценка фитнеса вынесена из алгоритма наружу, что позволяет единообразно сравнивать все алгоритмы на общем стенде.
Результаты тестов
На полном стандартном стенде COA набрал 63.43%, высокий результат.
COA(Coyote)|Coyote Optimization Algorithm|15.0|5.0|
=============================
5 Hilly's; Func runs: 10000; result: 0.8890955934993101
25 Hilly's; Func runs: 10000; result: 0.7068146139756495
500 Hilly's; Func runs: 10000; result: 0.32718611958402094
=============================
5 Forest's; Func runs: 10000; result: 0.9946702798798231
25 Forest's; Func runs: 10000; result: 0.8535899004786041
500 Forest's; Func runs: 10000; result: 0.15152116248097336
=============================
5 Megacity's; Func runs: 10000; result: 0.8853333333333333
25 Megacity's; Func runs: 10000; result: 0.7104000000000001
500 Megacity's; Func runs: 10000; result: 0.18981333333333356
=============================
All score: 5.70842 (63.43%)
Композитный античит-тест даёт соответствующие высокие результаты:
COA(Coyote)|Coyote Optimization Algorithm|20.0|5.0|
=============================
Composite test: Hilly + Forest + Megacity + Peaks + Skin
Coordinates: 10; Epochs: 500; Repeats: 10
=============================
Run 1/10: 0.9866664583248237
Run 2/10: 0.9857396707649635
Run 3/10: 0.9954235254499133
Run 4/10: 0.9827156480516207
Run 5/10: 0.9866665766231328
Run 6/10: 0.9999999744010715
Run 7/10: 0.9989488644865328
Run 8/10: 0.9999999476895154
Run 9/10: 0.9866620346820829
Run 10/10: 0.9728312966736997
=============================
Average result: 0.9895653997 (98.96%)
=============================
Визуализация работы алгоритма на тестовом стенде, а также на некоторых других функциях.

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

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

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

COA на тестовой функции Shaffer

COA на тестовой функции GoldsteinPrice
По результатам работы алгоритм COA занимает 11 место в нашей рейтинговой таблице лучших оптимизационных методов.
| 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 | COA | coyote_optimization_algorithm | 0,88909 | 0,70681 | 0,32718 | 1,92308 | 0,99467 | 0,85358 | 0,15152 | 1,99977 | 0,88533 | 0,71040 | 0,18981 | 1,78554 | 5,708 | 63,43 |
| 12 | 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 |
| 13 | 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 |
| 14 | 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 |
| 15 | 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 |
| 16 | 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 |
| 17 | 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 |
| 18 | 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 |
| 19 | 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 |
| 20 | 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 |
| 21 | 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 |
| 22 | 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 |
| 23 | 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 |
| 24 | 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 |
| 25 | 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 |
| 26 | 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 |
| 27 | (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 |
| 28 | 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 |
| 29 | 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 |
| 30 | 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 |
| 31 | 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 |
| 32 | 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 |
| 33 | 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 |
| 34 | 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 |
| 35 | 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 |
| 36 | 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 |
| 37 | BWOm | beluga_whale_optimization_M | 0,78488 | 0,56872 | 0,29557 | 1,64917 | 0,91370 | 0,61760 | 0,12988 | 1,66118 | 0,81333 | 0,49946 | 0,15004 | 1,46283 | 4,773 | 53,04 |
| 38 | 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 |
| 39 | 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 |
| 40 | 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 |
| 41 | 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 |
| 42 | 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 |
| 43 | 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 |
| 44 | 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 |
| 45 | 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 |
| 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 | |
Выводы
Вспомним, с чем трейдер пришёл к нашей статье. Преждевременная сходимость как главный враг любого, кто ищет не один переоптимизированный максимум, а устойчивую область хороших параметров. Вопрос, который привёл сюда: существует ли механизм, способный исследовать несколько областей пространства параллельно, не жертвуя скоростью сходимости?
Coyote Optimization Algorithm отвечает на этот вопрос структурно. Его ответ — не единый рой, дышащий как одно целое, а архипелаг изолированных стай, каждая со своим лидером и своей культурой, изредка обменивающихся особями. Разделение труда между операторами оказалось на редкость чистым: стайная структура удерживает разнообразие, социальный ход уточняет решения, медиана-тенденция придаёт стае устойчивый к выбросам ориентир, кроссовер щенка добавляет комбинаторные скачки в недостижимые для плавного движения углы пространства, а механика возрастов непрерывно ротирует состав. Ни один оператор не дублирует другой.
На полном стандартном стенде COA набрал 63.43%, что выводит его в верхнюю часть рейтинговой таблицы серии. COA — сильный оптимизатор именно того диапазона размерностей, в котором живёт оптимизация торговых систем, где параметров обычно десятки, а не сотни.
Главный же результат — не абсолютная цифра, а её честность. Композитный античит-тест (пять разных функций — Hilly, Forest, Megacity, Peaks, Skin — с оптимумами, сдвинутыми с диагонали) дал 98.96%. Это принципиально важно. COA прошёл его идеально, и это доказывает, что его высокий результат на стандартном стенде заработан настоящим поиском, а не артефактом геометрии тестовых функций. Алгоритм честен.
В руках у читателя теперь рабочая реализация C_AO_COA_Coyote в стандартной архитектуре серии, разобранная по методам и подкреплённая псевдокодом; понимание социальной механики COA на уровне, достаточном, чтобы объяснить её коллеге на пальцах; и честная, проверенная анти-читом оценка, помещающая алгоритм в верхнюю часть рейтинга. Получен и ответ на исходный практический вопрос: стайная структура действительно даёт параллельное удержание нескольких областей поиска, а не служит красивой обёрткой над обычным роем — и сочетание этой структуры с встроенным кроссовером противостоит преждевременной сходимости, ровно той проблеме, с которой всё началось.
Отдельно стоит отметить методологический урок самой серии. Высокая цифра на стандартном стенде сама по себе ничего не гарантирует, пока её не подтвердил композитный анти-чит. Именно эта дисциплина — не верить красивому результату, пока он не выдержал проверку на off-diagonal задачах, — и отличает честное сравнение алгоритмов от рекламы их авторов. В случае COA проверка пройдена, и алгоритм заслуженно занимает высокое место.
Для трейдера, выбирающего оптимизатор под систему с десятками параметров и изрезанным ландшафтом прибыли, COA — крепкий, честный кандидат, который не схлопывается в первый локальный пик и держит разнообразие там, где обычный рой его теряет.

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

Рисунок 3. Гистограмма результатов тестирования алгоритмов (по шкале от 0 до 100, чем больше, тем лучше, где 100 — максимально возможный теоретический результат), в архиве скрипт для расчёта рейтинговой таблицы
Плюсы и минусы алгоритма COA:
Плюсы:
- Небольшое количество внешних параметров.
- Хорошая сходимость.
Минусы:
- При небольшой популяции увеличивается разброс результатов.
К статье прикреплён архив с актуальными версиями кодов алгоритмов. Автор статьи не несёт ответственности за абсолютную точность в описании канонических алгоритмов, во многие из них были внесены изменения для улучшения поисковых возможностей. Выводы и суждения, представленные в статьях, основываются на результатах проведённых экспериментов.
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 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_COA.mq5 | Скрипт | Испытательный стенд для COA |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Разработка инструментария для анализа Price Action (Часть 65): Создание системы для мониторинга и анализа построенных вручную уровней Фибоначчи
Моделирование рынка: Position View (II)
Изучение стандартной библиотеки MQL5 (часть 1): Знакомство с CTrade, CiMA и CiATR
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования