preview
Алгоритм оптимизации койотов — Coyote Optimization Algorithm (COA)

Алгоритм оптимизации койотов — Coyote Optimization Algorithm (COA)

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

Содержание

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


Введение

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

Есть задача оптимизации, есть понимание, что один рой, сходящийся в одну точку, — это лотерея. Напрашивается вопрос: существуют ли алгоритмы, которые умеют исследовать несколько областей пространства параллельно, не теряя при этом скорости сходимости? В нашей серии статей о популяционных алгоритмах оптимизации мы уже разобрали не один десяток метаэвристик — от классических роевых до самых экзотических — и прогнали каждую через единый испытательный стенд: функции 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.

COA_scheme

Рисунок 1. Алгоритм оптимизации койотов: социальная структура поиска

На иллюстрации популяция разделена на изолированные стаи, каждая из которых занимает собственный регион мультимодального ландшафта. Каждая стая следует своему собственному альфе (лучшему койоту стаи), а не единому глобальному лидеру, что естественным образом сохраняет разнообразие. Социальная тенденция стаи — это покоординатная медиана её членов; она устойчива к выбросам, в отличие от среднего значения. Редкая миграция (вероятность p_leave = 0.005·nCoy² на итерацию, примерно один раз в 8 итераций при nCoy = 5) меняет местами двух койотов между двумя случайными стаями, распространяя успешные признаки по всему архипелагу.

  1. Социальное движение: каждый койот одновременно делает шаг в сторону альфы и в сторону культуры стаи, используя двух случайных соплеменников (rc1 и rc2) как точки отсчёта; новая позиция принимается только если она строго улучшает решение.

  2. Рождение детёныша: один раз за итерацию каждая стая производит детёныша с помощью бинарного кроссовера двух случайных родителей — как минимум один ген гарантированно наследуется от каждого родителя, а каждый ген мутирует в новое случайное значение с вероятностью Ps = 1/D.

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

Псевдокод алгоритма 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%)
=============================

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

Hilly

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

Forest

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

Megacity

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

Shaffer

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

GoldsteinPrice

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 — крепкий, честный кандидат, который не схлопывается в первый локальный пик и держит разнообразие там, где обычный рой его теряет.

tab

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

chart

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


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

Плюсы:

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

Минусы:

  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_COA.mq5
Скрипт Испытательный стенд для COA

Прикрепленные файлы |
COA_Coyote.zip (424.87 KB)
Разработка инструментария для анализа Price Action (Часть 65): Создание системы для мониторинга и анализа построенных вручную уровней Фибоначчи Разработка инструментария для анализа Price Action (Часть 65): Создание системы для мониторинга и анализа построенных вручную уровней Фибоначчи
Инструмент коррекции Фибоначчи – важный элемент анализа Price Action, указывающий ключевые уровни возможной рыночной реакции. Однако его эффективность часто ограничена необходимостью постоянного ручного наблюдения, из-за чего часть сетапов может быть пропущена. В этой части серии представлен инструмент, который с помощью MQL5 синхронизирует и активно отслеживает вручную построенные уровни Фибоначчи, сочетая дискреционный подход с автоматизированным контролем.
Моделирование рынка: Position View (II) Моделирование рынка: Position View (II)
В этой статье я покажу как максимально просто и практично использовать индикатор для отслеживания открытых позиций на торговом сервере. Я делаю это именно так, шаг за шагом, чтобы показать, что вам не обязательно переносить всё это в советник. Многие из вас, вероятно, уже привыкли к этому по той или иной причине. На самом деле это ерунда, так как по мере развития данной реализации станет ясно, что вы сможете создавать или реализовать различные типы индикаторов для этой цели.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Изучение стандартной библиотеки MQL5 (часть 1): Знакомство с CTrade, CiMA и CiATR Изучение стандартной библиотеки MQL5 (часть 1): Знакомство с CTrade, CiMA и CiATR
Стандартная библиотека MQL5 — чрезвычайно полезный инструмент при разработке торговых алгоритмов для MetaTrader 5. В этой серии мы будем учиться создавать с помощью нее эффективные торговые инструменты для MetaTrader 5. Под инструментами подразумеваются собственные советники, индикаторы и другие вспомогательные средства. Сегодня мы разработаем трендового советника с использованием классов CTrade, CiMA и CiATR. Тема будет полезна всем — и начинающим, так и опытным разработчикам. Приятного чтения.