preview
Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA)

Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA)

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

Содержание

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


Введение

Задача глобальной оптимизации присутствует во многих прикладных задачах — от инженерии до автоматизированных торговых систем — и в реальных прикладных сценариях часто сталкивается с мультимодальными, разрывными и смешанными по типу пространствами, где классические градиентные методы неприменимы. В таких условиях востребованы метаэвристики, в том числе роевые алгоритмы. В этой статье мы рассматриваем Artificial Searching Swarm Algorithm (ASSA) — алгоритм с необычной тактической метафорой, в котором агенты обмениваются короткими «сигналами» о найденных улучшениях. Наша проверяемая гипотеза: позволит ли такой краткоживущий сигнал ускорить раннюю сходимость без потери способности роя выбираться из локальных экстремумов при фиксированном бюджете вычислений?

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

Алгоритм Artificial Searching Swarm Algorithm (ASSA), был опубликован в 2012 году. Работа демонстрирует результаты тестирования ASSA на десяти контрольных функциях — от классических Розенброка, Гриванка и Растригина до задач с ограничениями — и проводит сравнение с генетическим алгоритмом, PSO и AFSA. Авторы показывают, что при достаточном числе итераций ASSA устойчиво превосходит конкурентов в задачах высокой размерности, сохраняя при этом простоту реализации и нечувствительность к начальным условиям.

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

Механизм глобального табло, известный в биомиметической литературе как "Bulletin Board", служит долгосрочной памятью алгоритма. В отличие от PSO, где каждая частица несёт полную информацию о своей траектории, в ASSA агент хранит лишь свой личный рекорд; актуальный глобальный рекорд всего роя обновляется централизованно и доступен всем участникам одновременно.


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

Каждый агент на каждой итерации при работе алгоритма ASSA совершает ровно одно движение, выбирая его тип из трёх возможных правил в зависимости от текущего состояния системы.

Роем управляет механизм сигнала. Когда любой агент обнаруживает позицию с лучшим значением фитнес-функции, чем у него была в предыдущей итерации, он транслирует свои координаты. Эта позиция запоминается в переменной "Xcall" и остаётся активной ровно одну итерацию. На следующей итерации каждый агент знает, был ли в предыдущей итерации зафиксирован сигнал, и с вероятностью "Pc" решает откликнуться на него. Если за итерацию никто не улучшился — сигнал гаснет, и все агенты переходят в режим самостоятельной разведки.

Параллельно ведётся глобальное табло (Bulletin Board) — постоянно обновляемая запись лучшей позиции и лучшего значения фитнес-функции за всё время работы алгоритма. Это глобальный ориентир, доступный каждому агенту при выборе направления движения.

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

Три правила движения. Все перемещения агентов реализованы в нормализованном пространстве поиска. Нормализация нужна для того, чтобы координаты с разными масштабами вносили одинаковый вклад в вычисление расстояния и направления. Нормализованное евклидово расстояние между двумя точками "A" и "B" в пространстве размерности "n" вычисляется как

NormDist(A, B) = sqrt( Σ ((A[c] - B[c]) / (ub[c] - lb[c]))² ),

где "ub" и "lb" — верхняя и нижняя границы области поиска по координате "c".

Длина шага в нормализованном пространстве задаётся параметром "stepRatio", рекомендуемые значения которого лежат в диапазоне 0.15–0.30. В абсолютных единицах по каждой координате это составляет:

stepRatio · (ub − lb).

Правило 1 — Синергетическое движение. Применяется, если в предыдущей итерации был зафиксирован сигнал вызова (callActive = true) и случайное число r₀ ∈ [0, 1] оказалось меньше параметра вероятности "Pc". Агент делает один шаг в направлении позиции вызывающего "Xcall":

X_new[c] = X[c] + (Xcall[c] − X[c]) / NormDist(X, Xcall) · r₁ · stepRatio

Деление на "NormDist" превращает вектор направления в единичный в нормализованном пространстве, поэтому фактическая длина шага по каждой координате составляет ровно: r₁ · stepRatio · (ub − lb) вне зависимости от масштаба координат. Множитель r₁ ∈ [0, 1] вносит случайность в длину шага — агент не всегда доходит до полного шага.

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

Правило 2 — Поисковое движение. Применяется, если сигнал отсутствует или агент не откликнулся на него. Агент строит точку-зонд, притягиваясь одновременно к своему личному рекорду "cB_i" и к глобальному рекорду "cB":

probe[c] = X[c] + r₁ · (cB_i[c] − X[c]) + r₂ · (cB[c] − X[c]),

где r₁, r₂ ∈ [0, 1] — независимые случайные числа.

Если личный рекорд совпадает с текущей позицией (или оба рекорда уже рядом), зонд практически не сдвигается с места — это ситуация вырождения, и алгоритм переходит к правилу 3.

Перед вычислением расстояния зонд ограничивается границами области поиска. Это принципиально важно: при r₁ + r₂ > 1 точка "probe" может значительно выйти за пределы допустимой области, что раздует "NormDist" до огромных значений и фактически обнулит реальный шаг агента. После клипирования агент делает шаг в направлении зонда:

X_new[c] = X[c] + (probe[c] − X[c]) / NormDist(X, probe) · r₃ · stepRatio.

Смысл правила: использовать накопленную коллективную память для направленного поиска вблизи известных хороших зон, уходя при этом от стагнации.

Правило 3 — Случайное движение. Применяется, если зонд правила 2 практически совпал с текущей позицией агента (NormDist < 1e-10), что означает отсутствие полезного градиентного сигнала. Агент совершает случайный шаг по каждой координате независимо:

X_new[c] = X[c] + rand(−1, 1) · stepRatio · (ub[c] − lb[c])

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

После любого из трёх движений новая позиция пропускается через функцию "SeInDiSp", что обеспечивает корректность при дискретных пространствах поиска.

Обновление состояния. После того как внешний оценщик вычислил значения фитнес-функции для всех новых позиций, алгоритм обновляет своё внутреннее состояние. Для каждого индивида проверяется, улучшился ли он по сравнению с предыдущей итерацией. Агент с наилучшим улучшением среди всех агентов становится новым источником сигнала на следующую итерацию — его позиция записывается в "Xcall". Если ни один агент не улучшился, сигнал гаснет (callActive = false).

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

Личный рекорд агента обновляется только в случае улучшения. Глобальное табло обновляется сразу, как только любой агент превысил записанный на нём результат.

ASSA

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

Иллюстрация содержит все ключевые элементы алгоритма в одном кадре. Левая часть — поисковое пространство: фоновый градиент имитирует ландшафт фитнес-функции, а золотая звезда с кольцами — Global Best / Xcall.

Три агента с цветовой кодировкой демонстрируют каждое правило: жёлтая стрелка (Rule 1) летит к "Xcall", голубая (Rule 2) идёт к точке "probe" через личный и глобальный рекорды, оранжевые (Rule 3) расходятся веером случайных направлений. Красный агент с импульсными кольцами показывает момент сигнала. Серые неактивные агенты — фон роя.

Правая панель:

  • Bulletin Board с полями "cB" и "fB"
  • Три блока правил с формулами
  • Механизм сигнала — четыре шага от улучшения до гашения флага
  • Таблица параметров с рекомендуемыми диапазонами.

Теперь напишем псевдокод алгоритма ASSA.

=== ИНИЦИАЛИЗАЦИЯ ===
FOR i = 0 TO N-1:
    FOR c = 0 TO n-1:
        X[i][c] ← случайное число из [lb[c], ub[c]]
callActive ← FALSE
revision   ← FALSE

=== ПЕРВАЯ ОЦЕНКА (revision = FALSE) ===
Moving:
    FOR i = 0 TO N-1:
        a[i].c ← SeInDiSp(X[i])          // привязать к сетке
[внешний оценщик вычисляет a[i].f для всех i]
Revision:
    FOR i = 0 TO N-1:
        a[i].fP ← a[i].f                  // базовое значение для детектора улучшений
        a[i].fB ← a[i].f                  // личный рекорд
        a[i].cB ← a[i].c                  // позиция личного рекорда
        a[i].cP ← a[i].c                  // текущая позиция
        IF a[i].f > fB:
            fB ← a[i].f
            cB ← a[i].c                   // глобальное табло

    Xcall      ← cB                       // начальный сигнал — от лучшего агента
    callActive ← TRUE
    revision   ← TRUE

=== ОСНОВНОЙ ЦИКЛ ===
WHILE бюджет не исчерпан:
    // --- MOVING ---
    FOR i = 0 TO N-1:
        moved ← FALSE

        // ПРАВИЛО 1 — Синергетическое движение
        IF callActive AND rand(0,1) < Pc:
            dist ← NormDist(a[i].cP, Xcall)
            IF dist > 1e-10:
                r₁ ← rand(0, 1)
                FOR c = 0 TO n-1:
                    xNew ← a[i].cP[c] + (Xcall[c] − a[i].cP[c]) / dist · r₁ · stepRatio
                    a[i].c[c] ← SeInDiSp(Clamp(xNew, lb[c], ub[c]))
                moved ← TRUE

        IF NOT moved:
            // ПРАВИЛО 2 — Поисковое движение
            r₁ ← rand(0, 1)
            r₂ ← rand(0, 1)
            FOR c = 0 TO n-1:
                probe[c] ← Clamp(a[i].cP[c] + r₁·(a[i].cB[c] − a[i].cP[c])
                                              + r₂·(cB[c]      − a[i].cP[c]),
                                  lb[c], ub[c])
            dist ← NormDist(a[i].cP, probe)

            IF dist > 1e-10:
                r₃ ← rand(0, 1)
                FOR c = 0 TO n-1:
                    xNew ← a[i].cP[c] + (probe[c] − a[i].cP[c]) / dist · r₃ · stepRatio
                    a[i].c[c] ← SeInDiSp(Clamp(xNew, lb[c], ub[c]))
            ELSE:
                // ПРАВИЛО 3 — Случайное движение
                FOR c = 0 TO n-1:
                    xNew ← a[i].cP[c] + rand(−1, 1) · stepRatio · (ub[c] − lb[c])
                    a[i].c[c] ← SeInDiSp(Clamp(xNew, lb[c], ub[c]))
    [внешний оценщик вычисляет a[i].f для всех i]

    // --- REVISION ---
    newCall  ← FALSE
    bestImpr ← −∞

    FOR i = 0 TO N-1:
        // детектор улучшения → кандидат на новый сигнал
        IF a[i].f > a[i].fP AND a[i].f > bestImpr:
            bestImpr ← a[i].f
            newCall  ← TRUE
            Xcall    ← a[i].c

        // обновить личный рекорд
        IF a[i].f > a[i].fB:
            a[i].fB ← a[i].f
            a[i].cB ← a[i].c

        // безусловно принять новую позицию
        a[i].fP ← a[i].f
        a[i].cP ← a[i].c

        // обновить глобальное табло
        IF a[i].f > fB:
            fB ← a[i].f
            cB ← a[i].c

    callActive ← newCall

=== РЕЗУЛЬТАТ ===
RETURN cB, fB            // позиция и значение глобального оптимума

Переходим к написанию кода алгоритма. Класс "C_AO_ASSA" является наследником базового класса "C_AO" и реализует все три поведенческих правила алгоритма искусственного поискового роя. Публичный интерфейс класса полностью совпадает с интерфейсом остальных алгоритмов серии "C_AO" — методы "Init", "Moving" и "Revision" делают его совместимым с унифицированным тестовым стендом без каких-либо изменений во внешнем коде.

Из полей базового класса алгоритм использует: a[] — массив агентов; a[i].c[] — позиция для оценки фитнес-функции; a[i].f — значение фитнес-функции, заполняемое внешним оценщиком; a[i].cP[] — текущая позиция агента, сохраняемая между итерациями; a[i].cB[] и a[i].fB — позиция и значение личного рекорда; a[i].fP — значение фитнес-функции в предыдущей позиции, используемое для детектирования улучшения; cB[] и fB — глобальное табло (позиция и значение лучшего решения за всё время); revision — флаг завершения фазы инициализации.

Собственные поля класса, не имеющие аналогов в базовом классе, составляют всего четыре элемента, "stepRatio" хранит шаг поиска как долю от длины диапазона по каждой оси, "Pc" задаёт вероятность синергетического движения, "callActive" — булев флаг, обозначающий наличие активного сигнала вызова, действующего ровно одну итерацию. Xcall[] — массив координат, фиксирующий позицию агента, который последним транслировал улучшение. Рабочий массив probe[] выделяется один раз при инициализации и повторно используется в каждой итерации для вычисления зонда правила 2.

Метод "SetParams" переносит значения из универсального массива params[] в типизированные поля класса. Вызывается один раз из тестового скрипта после того, как пользователь через входные параметры задал нужные значения. 

//————————————————————————————————————————————————————————————————————
class C_AO_ASSA : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_ASSA () { }
  C_AO_ASSA ()
  {
    ao_name = "ASSA";
    ao_desc = "Artificial Searching Swarm Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/21671";

    popSize   = 20;
    stepRatio = 0.25;
    Pc        = 0.01;

    ArrayResize (params, 3);
    params [0].name = "popSize";   params [0].val = popSize;
    params [1].name = "stepRatio"; params [1].val = stepRatio;   // AS_step = stepRatio*(ub-lb), recommended 0.15-0.30
    params [2].name = "Pc";        params [2].val = Pc;          // synergetic probability, recommended 0.001-0.1
  }

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

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  double stepRatio;   // step as a fraction of the search range length
  double Pc;          // probability that an agent responds to a call (Rule 1)

  private: //---------------------------------------------------------
  double Xcall [];    // position of the agent that last sent a call
  bool   callActive;  // is there an active call signal?
  double probe [];    // reusable workspace for Rule-2 candidate point (avoids per-call alloc)

  // Returns the Euclidean distance in coordinate-normalised space [0,1]^n.
  // Normalising by (ub-lb) per coordinate makes steps scale-independent.
  double NormDist (double &A [], double &B []);
};
//————————————————————————————————————————————————————————————————————

Метод инициализации подготавливает алгоритм к новому запуску. Первым делом вызывается "StandardInit" базового класса, который сбрасывает флаг "revision" в "false", обнуляет глобальный рекорд "fB", выделяет и инициализирует массив агентов a[], копирует переданные диапазоны поиска во внутренние массивы. После возврата из "StandardInit" выделяется память под собственные массивы класса: Xcall[] размером "coords" для хранения позиции сигнала и probe[] того же размера для рабочего вычисления зонда. Флаг "callActive" сбрасывается в "false" — никакого сигнала ещё нет.

Затем каждый агент помещается в случайную начальную позицию: для каждой координаты вызывается u.RNDfromCI, и результат записывается в a[i].cP[c] . Именно cP[], а не c[], является текущей позицией агента между итерациями. Поле c[] на этом этапе не заполняется — оно будет установлено при первом вызове "Moving".

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

  //------------------------------------------------------------------
  ArrayResize (Xcall, coords);
  ArrayResize (probe, coords);

  callActive = false;

  // Place agents at random positions; cP[] is the "live" position between iterations.
  for (int i = 0; i < popSize; i++)
    for (int c = 0; c < coords; c++)
      a [i].cP [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);

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

Вспомогательный метод вычисляет евклидово расстояние между двумя точками "A" и "B" в нормализованном пространстве [0, 1]^n . Нормализация выполняется покоординатно.

Возвращаемое значение используется в двух ролях. Во-первых, как делитель для нормировки вектора направления — именно деление на "NormDist" превращает вектор (TO − X) в единичный в нормализованном пространстве, и тогда произведение r · stepRatio задаёт длину шага в долях от диапазона независимо от абсолютного масштаба. Во-вторых, как пороговая проверка — если расстояние меньше 1e-10, правило 2 считает зонд совпадающим с текущей позицией и передаёт управление правилу 3.

//————————————————————————————————————————————————————————————————————
double C_AO_ASSA::NormDist (double &A [], double &B [])
{
  double s = 0.0;
  for (int c = 0; c < coords; c++)
  {
    double rng = rangeMax [c] - rangeMin [c];
    if (rng < 1e-10) continue;
    double d = (A [c] - B [c]) / rng;
    s += d * d;
  }
  return MathSqrt (s);
}
//————————————————————————————————————————————————————————————————————

Метод реализует фазу движения всех агентов и делится на два принципиально разных блока. В самом первом вызове "Moving" флаг "revision" ещё ложен — инициализация не завершена. В этом случае метод просто переводит начальные позиции из a[i].cP[] в a[i].c[] через u.SeInDiSp(), привязывая каждую координату к ближайшему допустимому узлу дискретной сетки. Эти значения будут переданы внешнему оценщику, который вычислит начальные значения фитнес-функции. Никакого движения ещё не происходит.

Нормальные итерации (при revision = true). Для каждого агента метод последовательно проверяет три правила, переходя к следующему только если предыдущее не применилось. Флаг "moved" отслеживает, было ли выполнено движение по правилу 1.

Правило 1 — синергетическое движение. Проверяется одновременно активность сигнала "callActive" и случайное число u.RNDfromCI(0,1) < Pc. Если оба условия выполнены, вычисляется нормализованное расстояние от текущей позиции a[i].cP до позиции сигнала "Xcall". При ненулевом расстоянии тянется случайное число "r1" и выполняется шаг: xNew = cP[c] + (Xcall[c] − cP[c]) / dist * r1 * stepRatio. Результат укладывается в границы диапазона через вспомогательный метод Clamp() и через SeInDiSp(). Флаг "moved" поднимается.

Правило 2 — поисковое движение. Выполняется, если "moved" ложен. Независимо разыгрываются "r1" и "r2", после чего для каждой координаты вычисляется зонд: probe[c] = cP[c] + r1*(cB_i[c] − cP[c]) + r2*(cB[c] − cP[c]). Зонд немедленно укладывается в границы — это принципиально важный шаг: при r1 + r2 > 1 сырое значение зонда уходит за пределы допустимой области, и без "NormDist" раздувается до значений, при которых реальный шаг агента вырождается в ноль. После этого вычисляется нормализованное расстояние до зонда и при ненулевом расстоянии разыгрывается "r3" и выполняется шаг в направлении зонда.

Правило 3 — случайное движение. Применяется, если "NormDist" до зонда оказался меньше порога 1e-10, то есть зонд практически совпал с текущей позицией агента. По каждой координате независимо выполняется случайное смещение: xNew = cP[c] + u.RNDfromCI(−1, 1) * stepRatio * (rangeMax[c] − rangeMin[c]).

Во всех трёх случаях финальный "xNew" проходит через SeInDiSp() перед записью в a[i].c[c].

//————————————————————————————————————————————————————————————————————
void C_AO_ASSA::Moving ()
{
  //--- Pass 0 (before first Revision): expose initial random positions
  // `revision` is the base-class flag; it is false until Revision() sets it true.
  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;
  }

  //--- Normal iterations --------------------------------------------
  for (int i = 0; i < popSize; i++)
  {
    bool moved = false;

    //================================================================
    // RULE 1 — Synergetic move
    //   If an active call exists AND rand < Pc, move one step toward Xcall.
    //
    //   Formula (normalised-space step toward Xcall):
    //     X_new[c] = X[c] + (Xcall[c] - X[c]) / NormDist(X, Xcall) * r1 * stepRatio
    //
    //   The division by NormDist converts the real-space direction vector into
    //   a unit vector in normalised space, so the step length equals
    //   r1 * stepRatio * (ub[c] - lb[c]) regardless of coordinate scale.
    //================================================================
    if (callActive && u.RNDfromCI (0.0, 1.0) < Pc)
    {
      double dist = NormDist (a [i].cP, Xcall);
      if (dist > 1e-10)
      {
        double r1 = u.RNDfromCI (0.0, 1.0);
        for (int c = 0; c < coords; c++)
        {
          double xNew = a [i].cP [c]
                      + (Xcall [c] - a [i].cP [c]) / dist * r1 * stepRatio;
          a [i].c [c] = u.SeInDiSp (xNew, rangeMin [c], rangeMax [c], rangeStep [c]);
        }
        moved = true;
      }
    }

    if (!moved)
    {
      //==============================================================
      // RULE 2 — Search move
      //   Build candidate probe attracted toward personal best (cB / Xs)
      //   and global best (cB_global / Xg), then step toward it.
      //
      //   Probe formula:
      //     probe[c] = X[c] + r1*(Xs[c] - X[c]) + r2*(Xg[c] - X[c])
      //
      //   probe is clamped to [lb, ub] before computing direction:
      //   when r1+r2 > 1 the raw probe flies outside the domain,
      //   causing NormDist to blow up and the actual step to shrink to ~0.
      //
      //   Step formula (normalised-space direction toward probe):
      //     X_new[c] = X[c] + (probe[c] - X[c]) / NormDist(X, probe) * r3 * stepRatio
      //
      //   Falls through to Rule 3 when probe == X (NormDist < threshold).
      //==============================================================
      double r1 = u.RNDfromCI (0.0, 1.0);
      double r2 = u.RNDfromCI (0.0, 1.0);

      for (int c = 0; c < coords; c++)
      {
        probe [c] = a [i].cP [c] + r1 * (a [i].cB [c] - a [i].cP [c]) + r2 * (cB [c] - a [i].cP [c]);
      }

      double dist = NormDist (a [i].cP, probe);

      if (dist > 1e-10)
      {
        double r3 = u.RNDfromCI (0.0, 1.0);
        for (int c = 0; c < coords; c++)
        {
          double xNew = a [i].cP [c] + (probe [c] - a [i].cP [c]) / dist * r3 * stepRatio;
          a [i].c [c] = u.SeInDiSp (xNew, rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
      else
      {
        //============================================================
        // RULE 3 — Stochastic move (fallback when probe == X)
        //   Random displacement in [-stepRatio, +stepRatio] * (ub - lb) per coord.
        //
        //   Formula:
        //     X_new[c] = X[c] + rand(-1, 1) * stepRatio * (ub[c] - lb[c])
        //============================================================
        for (int c = 0; c < coords; c++)
        {
          double xNew = a [i].cP [c] + u.RNDfromCI (-1.0, 1.0) * stepRatio * (rangeMax [c] - rangeMin [c]);
          a [i].c [c] = u.SeInDiSp (xNew, rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    }
  }
}
//————————————————————————————————————————————————————————————————————

Метод "Revision" реализует фазу обновления внутреннего состояния алгоритма после того, как внешний оценщик заполнил значения a[i].f. Как и "Moving", метод разделён на два блока.

Проход нулевой итерации (при revision = false). После первой оценки фитнес-функции метод инициализирует все исторические записи. Для каждого агента: текущее значение a[i].f записывается в a[i].fP как базовое значение для будущего детектирования улучшений; то же значение записывается в a[i].fB как начальный личный рекорд; позиция a[i].c копируется в a[i].cB и a[i].cP. Параллельно отслеживается глобальный рекорд: лучший агент обновляет поля "fB" и "cB" базового класса. По завершении цикла позиция глобального лучшего копируется в "Xcall", флаг "callActive" поднимается в "true" — чтобы уже с первой рабочей итерации агенты могли использовать синергетическое правило. Флаг "revision" переводится в "true", фиксируя завершение инициализации.

Нормальные итерации (при revision = true). Перед циклом по агентам сбрасываются переменные детектирования нового сигнала: newCall = false и bestImpr = −DBL_MAX. Затем для каждого агента выполняется четыре независимые проверки.

Первая проверка: если a[i].f > a[i].fP (агент улучшился по сравнению с прошлой итерацией) и его новое значение лучше любого другого улучшения в текущей итерации, этот агент становится новым источником сигнала — его позиция записывается в "Xcall", флаг "newCall" поднимается.

Вторая проверка: если a[i].f > a[i].fB, обновляется личный рекорд агента — "fB" и "cB" агента получают новые значения.

Третья операция: безусловное принятие нового положения. Значение a[i].fP получает a[i].f, позиция a[i].cP получает a[i].c. Это намеренное архитектурное решение: в ASSA агент всегда переходит туда, куда сделал шаг, вне зависимости от того, было ли это движение улучшением. Такая безусловная диффузия поддерживает непрерывное исследование пространства.

Четвёртая проверка: если a[i].f > fB, обновляется глобальное табло. По завершении цикла флаг "callActive" получает значение "newCall". Если ни один агент не улучшился — сигнал гаснет, и на следующей итерации все агенты перейдут в режим поиска или случайного движения.

//————————————————————————————————————————————————————————————————————
void C_AO_ASSA::Revision ()
{
  //--- Pass 0: first evaluation -> initialise all history records ---
  if (!revision)
  {
    for (int i = 0; i < popSize; i++)
    {
      a [i].fP = a [i].f;                           // baseline for improvement detection
      a [i].fB = a [i].f;                           // personal best fitness
      ArrayCopy (a [i].cB, a [i].c, 0, 0, coords); // personal best position
      ArrayCopy (a [i].cP, a [i].c, 0, 0, coords); // live position (grid-snapped)

      if (a [i].f > fB)
      {
        fB = a [i].f;
        ArrayCopy (cB, a [i].c, 0, 0, coords);
      }
    }

    // Seed the call signal with the global best so Rule 1 is immediately useful.
    ArrayCopy (Xcall, cB, 0, 0, coords);
    callActive = true;

    revision = true; // base-class flag: init phase is done
    return;
  }

  //--- Normal revision: update records, detect improvements, manage call
  //   The best-improving agent (highest new fitness) broadcasts its position
  //   as the call signal for the next iteration.
  bool   newCall  = false;
  double bestImpr = -DBL_MAX;

  for (int i = 0; i < popSize; i++)
  {
    // Detect improvement over the previous position -> this agent sends a call.
    if (a [i].f > a [i].fP && a [i].f > bestImpr)
    {
      bestImpr = a [i].f;
      newCall  = true;
      ArrayCopy (Xcall, a [i].c, 0, 0, coords);
    }

    // Update personal best (cB / Xs).
    if (a [i].f > a [i].fB)
    {
      a [i].fB = a [i].f;
      ArrayCopy (a [i].cB, a [i].c, 0, 0, coords);
    }

    // Accept move unconditionally: current position becomes the new baseline.
    a [i].fP = a [i].f;
    ArrayCopy (a [i].cP, a [i].c, 0, 0, coords);

    // Update global best — Bulletin Board (base fields cB / fB).
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy (cB, a [i].c, 0, 0, coords);
    }
  }

  callActive = newCall;
}
//————————————————————————————————————————————————————————————————————


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

Итоговый набранный балл 33,42% по серии испытаний, не позволяет ASSA войти в рейтинговую таблицу сорока пяти лучших методов оптимизации.

ASSA|Artificial Searching Swarm Algorithm|50.0|0.3|0.1|
=============================
5 Hilly's; Func runs: 10000; result: 0.6255894509557836
25 Hilly's; Func runs: 10000; result: 0.4138604978642843
500 Hilly's; Func runs: 10000; result: 0.25380653012476456
=============================
5 Forest's; Func runs: 10000; result: 0.5448792178119948
25 Forest's; Func runs: 10000; result: 0.2980777003474912
500 Forest's; Func runs: 10000; result: 0.185642399646027
=============================
5 Megacity's; Func runs: 10000; result: 0.41076923076923083
25 Megacity's; Func runs: 10000; result: 0.1787692307692308
500 Megacity's; Func runs: 10000; result: 0.09629230769230851
=============================
All score: 3.00769 (33.42%)

Обогатим наш набор тестовых функций ещё двумя общепринятыми эталонными задачами, чтобы исследователи могли проверять алгоритмы и на других типах ландшафтов. В частности, функция Rosenbrock применяется для оценки точности сходимости, а Griewank, как и Rastrigin — для анализа скорости и устойчивости сходимости. Заодно посмотрим, как алгоритм ASSA ведёт себя на этих новых тестовых функциях.

Griewank

ASSA на тестовой функции Griewank

Функция Гриванка была предложена Андреасом Гриванком в 1980 году в работе, посвящённой обобщённым методам спуска для глобальной оптимизации, и с тех пор прочно вошла в стандартный арсенал бенчмарков для сравнительного тестирования метаэвристик. В двумерном случае она записывается следующим образом: f(x, y) = (x² + y²) / 4000 − cos(x) · cos(y/√2) + 1

Структура функции представляет собой наложение двух принципиально разных компонентов. Первый — квадратичный параболоид с очень пологим подъёмом (делитель 4000 намеренно делает его почти плоским), формирующий общий градиентный фон, направляющий поиск к центру. Второй — произведение двух косинусов, создающее густую периодическую рябь по всей поверхности. Именно взаимодействие этих двух компонентов определяет сложность функции: косинусный член порождает огромное количество локальных оптимумов, тогда как квадратичный фон делает их неравноценными — чем дальше от центра, тем хуже локальный максимум. Глобальный оптимум единственен и расположен в точке (0, 0), где f(0, 0) = 0.

Заслуживает отдельного внимания несимметричность ячеек косинусной сетки. По оси x период равен 2π ≈ 6.28, тогда как по оси y аргумент косинуса делится на √2, что растягивает период до 2π√2 ≈ 8.89. В результате на 2D-визуализации характерные "острова" оптимумов имеют не квадратную, а прямоугольную форму, вытянутую по вертикали — это хорошо заметно на графике функции.

Для тестового стенда принята область поиска x, y ∈ [−20, 20]. Выбор этого диапазона не случаен и заслуживает отдельного пояснения. Классическая область поиска функции Гриванка в литературе задаётся как [−600, 600]. Однако при таком диапазоне период косинуса составляет менее одного процента от ширины экрана, и характерная сетка оптимумов визуально сливается в однородный фон, неотличимый от чистого параболоида.

При диапазоне [−20, 20] в поле зрения попадает около шести с половиной периодов по оси "x" и около пяти по оси "y", при этом амплитуда косинусного члена превышает вклад квадратичного члена в пять раз — рябь становится доминирующей визуально, и структура функции читается отчётливо. С точки зрения алгоритмической сложности уменьшение диапазона снижает общее число локальных оптимумов, однако не меняет принципиального характера задачи: алгоритм по-прежнему вынужден работать в мультимодальном ландшафте, где каждый шаг рискует привести в соседний локальный колодец. В многомерном случае (500 запусков, то есть 1000 переменных) сложность задачи возрастает многократно.

Поскольку тестовый стенд построен на максимизацию, функция Гриванка входит в него в инвертированном виде: вместо f(x, y) оценивается −f(x, y), что превращает глобальный минимум в глобальный максимум. Масштабирование в диапазон [0, 1] выполняется относительно максимального значения исходной функции ≈ 2.141, достигаемого вблизи точки (−15.716, −17.798). Ниже представлена реализация функции Гриванка.

//——————————————————————————————————————————————————————————————————————————————
class C_Griewank : public C_Function
{
  public: //===================================================================
  C_Griewank ()
  {
    fuName = "Griewank";

    //границы функции
    xMinRange = -20.0; xMaxRange = 20.0;
    yMinRange = -20.0; yMaxRange = 20.0;

    //координаты максимума (фреймворк: минимум исходной функции)
    globalMaxFunValue = 1.0;
    xGlobalMax        = 0.0;
    yGlobalMax        = 0.0;

    //координаты минимума (фреймворк: максимум исходной функции)
    globalMinFunValue = 0.0;
    xGlobalMin        = -15.715716;
    yGlobalMin        = -17.797798;
  }

  double Core (double x, double y)
  {
    double f = (x * x + y * y) / 4000.0
             - MathCos (x) * MathCos (y / MathSqrt (2.0))
             + 1.0;

    // инвертируем: минимум исходной f → максимум в шкале [0,1]
    return Scale (-f, -2.140734, 0.0, 0.0, 1.0);
  }
};
//——————————————————————————————————————————————————————————————————————————————

Еще одна функция, реализация и визуализация.

Rosenbrock

ASSA на тестовой функции Rosenbrock

Функция Розенброка была опубликована в 1960 году и по сей день остаётся одним из наиболее цитируемых тестов для алгоритмов оптимизации — не из-за обилия локальных оптимумов, а по прямо противоположной причине. В двумерном случае она определяется формулой: f(x, y) = 100·(y − x²)² + (1 − x)²

Функция имеет единственный глобальный минимум f(1, 1) = 0 и не обладает локальными минимумами — казалось бы, простейшая задача для любого оптимизатора. Однако именно топология её ландшафта делает её классическим испытанием. Минимум расположен на дне узкой, сильно искривлённой параболической долины, ориентированной вдоль кривой y = x². Поперечный градиент в этой долине огромен — малейшее отклонение от дна резко повышает значение функции. Продольный же градиент вдоль дна долины чрезвычайно пологий: функция убывает к минимуму медленно и почти незаметно для алгоритмов, ориентирующихся на величину улучшения за шаг, поэтому она очень хорошо подходит для определения точности сходимости алгоритмов.

Характерная форма ландшафта хорошо читается на 2D-визуализации: красная зона высоких значений (то есть малых значений исходной функции в инвертированном представлении) вытянута по изогнутой полосе, следующей параболе y = x², тогда как левый нижний угол уходит в синий — именно там, у точки (−2.048, −2.048), исходная функция достигает своего максимума ≈ 3905.926, что соответствует нулю в шкале фреймворка.

Трудность Розенброка для роевых алгоритмов носит специфический характер. Большинство метаэвристик эффективно работает в ситуации, когда пространство поиска содержит чёткие ориентиры — выраженные пики, которые притягивают агентов. Здесь же агент, попавший на дно долины, оказывается в ситуации почти полного отсутствия градиентного сигнала вдоль направления к оптимуму. Алгоритм, способный надёжно найти точку (1, 1), демонстрирует умение двигаться по едва уловимому спуску, не съезжая при этом со скользких стенок долины. В многомерном случае задача усложняется экспоненциально: долина в "n" измерениях становится n-мерной параболической трубкой, и вероятность случайно выйти из неё растёт с каждым добавленным измерением.

Область поиска для тестового стенда задана как x, y ∈ [−2.048, 2.048] — стандартный диапазон, принятый в большинстве сравнительных исследований. Функция входит в стенд также в инвертированном виде: оценивается −f(x, y), глобальный минимум превращается в глобальный максимум со значением 1.0 в масштабированной шкале. Ниже представлена реализация.

//——————————————————————————————————————————————————————————————————————————————
class C_Rosenbrock : public C_Function
{
  public: //===================================================================
  C_Rosenbrock ()
  {
    fuName = "Rosenbrock";

    //границы функции
    xMinRange = -2.048; xMaxRange = 2.048;
    yMinRange = -2.048; yMaxRange = 2.048;

    //координаты максимума (фреймворк: минимум исходной функции)
    globalMaxFunValue = 1.0;
    xGlobalMax        = 1.0;
    yGlobalMax        = 1.0;

    //координаты минимума (фреймворк: максимум исходной функции)
    globalMinFunValue = 0.0;
    xGlobalMin        = -2.048;
    yGlobalMin        = -2.048;
  }

  double Core (double x, double y)
  {
    double f = 100.0 * MathPow (y - x * x, 2) + MathPow (1.0 - x, 2);

    // инвертируем: минимум исходной f → максимум в шкале [0,1]
    return Scale (-f, -3905.926227, 0.0, 0.0, 1.0);
  }
};
//——————————————————————————————————————————————————————————————————————————————

Визуализация работы ASSA на тестовых функциях для определения рейтинга.

Hilly

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

Forest

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

Megacity

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

После серии испытаний алгоритм ASSA представлен в нашей рейтинговой таблице лучших оптимизационных методов только ознакомительно.

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 0,94948 0,84776 0,43857 2,23581 1,00000 0,92334 0,39988 2,32323 0,70923 0,63477 0,23091 1,57491 6,134 68,15
2 CLA code lock algorithm (joo) 0,95345 0,87107 0,37590 2,20042 0,98942 0,91709 0,31642 2,22294 0,79692 0,69385 0,19303 1,68380 6,107 67,86
3 AMOm animal migration optimization M 0,90358 0,84317 0,46284 2,20959 0,99001 0,92436 0,46598 2,38034 0,56769 0,59132 0,23773 1,39675 5,987 66,52
4 (P+O)ES (P+O) evolution strategies 0,92256 0,88101 0,40021 2,20379 0,97750 0,87490 0,31945 2,17185 0,67385 0,62985 0,18634 1,49003 5,866 65,17
5 CTA comet tail algorithm (joo) 0,95346 0,86319 0,27770 2,09435 0,99794 0,85740 0,33949 2,19484 0,88769 0,56431 0,10512 1,55712 5,846 64,96
6 TETA time evolution travel algorithm (joo) 0,91362 0,82349 0,31990 2,05701 0,97096 0,89532 0,29324 2,15952 0,73462 0,68569 0,16021 1,58052 5,797 64,41
7 SDSm stochastic diffusion search M 0,93066 0,85445 0,39476 2,17988 0,99983 0,89244 0,19619 2,08846 0,72333 0,61100 0,10670 1,44103 5,709 63,44
8 ECBO enhanced_colliding_bodies_optimization 0,93479 0,75747 0,32471 2,01697 0,97436 0,77446 0,23037 1,97919 0,88923 0,58061 0,15224 1,62208 5,618 62,43
9 BOAm billiards optimization algorithm M 0,95757 0,82599 0,25235 2,03590 1,00000 0,90036 0,30502 2,20538 0,73538 0,52523 0,09563 1,35625 5,598 62,19
10 AAm archery algorithm M 0,91744 0,70876 0,42160 2,04780 0,92527 0,75802 0,35328 2,03657 0,67385 0,55200 0,23738 1,46323 5,548 61,64
11 ESG evolution of social groups (joo) 0,99906 0,79654 0,35056 2,14616 1,00000 0,82863 0,13102 1,95965 0,82333 0,55300 0,04725 1,42358 5,529 61,44
12 SIA simulated isotropic annealing (joo) 0,95784 0,84264 0,41465 2,21513 0,98239 0,79586 0,20507 1,98332 0,68667 0,49300 0,09053 1,27020 5,469 60,76
13 EOm extremal_optimization_M 0,76166 0,77242 0,31747 1,85155 0,99999 0,76751 0,23527 2,00277 0,74769 0,53969 0,14249 1,42987 5,284 58,71
14 BBO biogeography based optimization 0,94912 0,69456 0,35031 1,99399 0,93820 0,67365 0,25682 1,86867 0,74615 0,48277 0,17369 1,40261 5,265 58,50
15 ACS artificial cooperative search 0,75547 0,74744 0,30407 1,80698 1,00000 0,88861 0,22413 2,11274 0,69077 0,48185 0,13322 1,30583 5,226 58,06
16 DA dialectical algorithm 0,86183 0,70033 0,33724 1,89940 0,98163 0,72772 0,28718 1,99653 0,70308 0,45292 0,16367 1,31967 5,216 57,95
17 BHAm black hole algorithm M 0,75236 0,76675 0,34583 1,86493 0,93593 0,80152 0,27177 2,00923 0,65077 0,51646 0,15472 1,32195 5,196 57,73
18 ASO anarchy society optimization 0,84872 0,74646 0,31465 1,90983 0,96148 0,79150 0,23803 1,99101 0,57077 0,54062 0,16614 1,27752 5,178 57,54
19 RFO royal flush optimization (joo) 0,83361 0,73742 0,34629 1,91733 0,89424 0,73824 0,24098 1,87346 0,63154 0,50292 0,16421 1,29867 5,089 56,55
20 AOSm atomic orbital search M 0,80232 0,70449 0,31021 1,81702 0,85660 0,69451 0,21996 1,77107 0,74615 0,52862 0,14358 1,41835 5,006 55,63
21 TSEA turtle shell evolution algorithm (joo) 0,96798 0,64480 0,29672 1,90949 0,99449 0,61981 0,22708 1,84139 0,69077 0,42646 0,13598 1,25322 5,004 55,60
22 BSA backtracking_search_algorithm 0,97309 0,54534 0,29098 1,80941 0,99999 0,58543 0,21747 1,80289 0,84769 0,36953 0,12978 1,34700 4,959 55,10
23 DE differential evolution 0,95044 0,61674 0,30308 1,87026 0,95317 0,78896 0,16652 1,90865 0,78667 0,36033 0,02953 1,17653 4,955 55,06
24 SRA successful restaurateur algorithm (joo) 0,96883 0,63455 0,29217 1,89555 0,94637 0,55506 0,19124 1,69267 0,74923 0,44031 0,12526 1,31480 4,903 54,48
25 BO bonobo_optimizer 0,77565 0,63805 0,32908 1,74278 0,88088 0,76344 0,25573 1,90005 0,61077 0,49846 0,14246 1,25169 4,895 54,38
26 CRO chemical reaction optimisation 0,94629 0,66112 0,29853 1,90593 0,87906 0,58422 0,21146 1,67473 0,75846 0,42646 0,12686 1,31178 4,892 54,36
27 BIO blood inheritance optimization (joo) 0,81568 0,65336 0,30877 1,77781 0,89937 0,65319 0,21760 1,77016 0,67846 0,47631 0,13902 1,29378 4,842 53,80
28 DOA dream_optimization_algorithm 0,85556 0,70085 0,37280 1,92921 0,73421 0,48905 0,24147 1,46473 0,77231 0,47354 0,18561 1,43146 4,825 53,62
29 BSA bird swarm algorithm 0,89306 0,64900 0,26250 1,80455 0,92420 0,71121 0,24939 1,88479 0,69385 0,32615 0,10012 1,12012 4,809 53,44
30 DEA dolphin_echolocation_algorithm 0,75995 0,67572 0,34171 1,77738 0,89582 0,64223 0,23941 1,77746 0,61538 0,44031 0,15115 1,20684 4,762 52,91
31 HS harmony search 0,86509 0,68782 0,32527 1,87818 0,99999 0,68002 0,09590 1,77592 0,62000 0,42267 0,05458 1,09725 4,751 52,79
32 SSG saplings sowing and growing 0,77839 0,64925 0,39543 1,82308 0,85973 0,62467 0,17429 1,65869 0,64667 0,44133 0,10598 1,19398 4,676 51,95
33 BCOm bacterial chemotaxis optimization M 0,75953 0,62268 0,31483 1,69704 0,89378 0,61339 0,22542 1,73259 0,65385 0,42092 0,14435 1,21912 4,649 51,65
34 ABO african buffalo optimization 0,83337 0,62247 0,29964 1,75548 0,92170 0,58618 0,19723 1,70511 0,61000 0,43154 0,13225 1,17378 4,634 51,49
35 (PO)ES (PO) evolution strategies 0,79025 0,62647 0,42935 1,84606 0,87616 0,60943 0,19591 1,68151 0,59000 0,37933 0,11322 1,08255 4,610 51,22
36 FBA fractal-based Algorithm 0,79000 0,65134 0,28965 1,73099 0,87158 0,56823 0,18877 1,62858 0,61077 0,46062 0,12398 1,19537 4,555 50,61
37 TSm tabu search M 0,87795 0,61431 0,29104 1,78330 0,92885 0,51844 0,19054 1,63783 0,61077 0,38215 0,12157 1,11449 4,536 50,40
38 BSO brain storm optimization 0,93736 0,57616 0,29688 1,81041 0,93131 0,55866 0,23537 1,72534 0,55231 0,29077 0,11914 0,96222 4,498 49,98
39 WOAm wale optimization algorithm M 0,84521 0,56298 0,26263 1,67081 0,93100 0,52278 0,16365 1,61743 0,66308 0,41138 0,11357 1,18803 4,476 49,74
40 AEFA artificial electric field algorithm 0,87700 0,61753 0,25235 1,74688 0,92729 0,72698 0,18064 1,83490 0,66615 0,11631 0,09508 0,87754 4,459 49,55
41 AEO artificial ecosystem-based optimization algorithm 0,91380 0,46713 0,26470 1,64563 0,90223 0,43705 0,21400 1,55327 0,66154 0,30800 0,28563 1,25517 4,454 49,49
42 CAm camel algorithm M 0,78684 0,56042 0,35133 1,69859 0,82772 0,56041 0,24336 1,63149 0,64846 0,33092 0,13418 1,11356 4,444 49,37
43 ACOm ant colony optimization M 0,88190 0,66127 0,30377 1,84693 0,85873 0,58680 0,15051 1,59604 0,59667 0,37333 0,02472 0,99472 4,438 49,31
44 CMAES covariance_matrix_adaptation_evolution_strategy 0,76258 0,72089 0,00000 1,48347 0,82056 0,79616 0,00000 1,61672 0,75846 0,49077 0,00000 1,24923 4,349 48,33
45 DA_duelist duelist_algorithm 0,92782 0,53778 0,27792 1,74352 0,86957 0,47536 0,18193 1,52686 0,62153 0,33569 0,11715 1,07437 4,345 48,28
ASSA artificial_searching_swarm_algorithm 0,62558 0,41386 0,25380 1,29324 0,54487 0,29807 0,18564 1,02858 0,41076 0,17876 0,09629 0,68581 3,008 33,42
RW random walk 0,48754 0,32159 0,25781 1,06694 0,37554 0,21944 0,15877 0,75375 0,27969 0,14917 0,09847 0,52734 2,348 26,09


Выводы

Мы предоставили воспроизводимую MQL5‑реализацию ASSA и проверили её поведение на нескольких типах ландшафтов. Результаты показали чёткую закономерность: механизм сигнала действительно ускоряет начальную концентрацию роя в перспективных зонах, но в зрелых итерациях из‑за фиксированной вероятности отклика (Pc), единичной продолжительности сигнала и безусловного принятия шагов алгоритм теряет разнообразие и склонен к преждевременной сходимости. Правило 2 усиленно тянет агентов к уже накопленным личным и глобальным рекордам, а правило 3 — случайного восстановления разнообразия — срабатывает редко и лишь как запасной вариант. Итоговый скоринг (33.42%) не позволил ASSA попасть в верхнюю часть рейтинга на тестовом стенде.

Практический вывод: ASSA полезен как компактный и понятный демонстрационный алгоритм и как исходная заготовка для модификаций, но в «сыром» виде он не рекомендован как основной инструмент для сложных мультимодальных или дискретных задач. Для повышения конкурентоспособности целесообразны следующие улучшения: адаптивное управление Pc и stepRatio (в зависимости от стадии поиска), продление или кумуляция сигнала (многотактные вызовы), регулярные механизмы диверсификации (периодические рестарты, ниширование, контролируемые мутации), а также более избирательная политика принятия шагов (не всегда принимать неулучшающие переходы). Исходный код и тестовый протокол оставлены открытыми для воспроизведения и дальнейших исследований.

tab

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

chart

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


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

Плюсы:

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

Минусы:

  1. Застревает. 

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



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

# Имя Тип Описание
1 #C_AO.mqh
Включаемый файл
Родительский класс популяционных алгоритмов оптимизации
2 #C_AO_enum.mqh
Включаемый файл
Перечисление популяционных алгоритмов оптимизации
3 TestFunctions.mqh
Включаемый файл
Библиотека тестовых функций
4
TestStandFunctions.mqh
Включаемый файл
Библиотека функций тестового стенда
5
Utilities.mqh
Включаемый файл
Библиотека вспомогательных функций
6
CalculationTestResults.mqh
Включаемый файл
Скрипт для расчета результатов в сравнительную таблицу
7
Testing AOs.mq5
Скрипт Единый испытательный стенд для всех популяционных алгоритмов оптимизации
8
Simple use of population optimization algorithms.mq5
Скрипт
Простой пример использования популяционных алгоритмов оптимизации без визуализации
9
Test_ASSA.mq5
Скрипт Испытательный стенд для ASSA
Прикрепленные файлы |
ASSA.zip (423.31 KB)
Возможности Мастера MQL5, которые вам нужно знать (Часть 72): Использование паттернов MACD и OBV с обучением с учителем Возможности Мастера MQL5, которые вам нужно знать (Часть 72): Использование паттернов MACD и OBV с обучением с учителем
В продолжение нашей предыдущей статьи о паре индикаторов MACD и OBV, мы рассмотрим, как эту пару можно улучшить с помощью машинного обучения. MACD и OBV — это взаимодополняющая пара, отражающая тренд и объем. Наш подход к машинному обучению использует сверточную нейронную сеть (convolution neural network, CNN), которая задействует экспоненциальное ядро (Exponential kernel) для определения размеров своих ядер и каналов при настройке прогнозов этой пары индикаторов. Как обычно, это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для создания советника.
Торговые инструменты на языке MQL5 (Часть 10): Разработка системы отслеживания стратегии с визуальными уровнями и показателями эффективности Торговые инструменты на языке MQL5 (Часть 10): Разработка системы отслеживания стратегии с визуальными уровнями и показателями эффективности
В данной статье мы разрабатываем систему отслеживания стратегий на языке MQL5, которая обнаруживает сигналы пересечения скользящих средних, отфильтрованные долгосрочной скользящей средней, моделирует или исполняет сделки с настраиваемыми уровнями TP и SL в пунктах, а также отслеживает результаты, такие как попадание в TP/SL, для анализа эффективности.
Прогнозирование OHLC-рядов Forex методом векторной авторегрессии (VAR) Прогнозирование OHLC-рядов Forex методом векторной авторегрессии (VAR)
В этом материале мы познакомимся с тем, как модели векторной авторегрессии (VAR) могут прогнозировать временные ряды значений OHLC (цены открытия, максимум, минимум и цена закрытия) на форексе Поговорим о том, как реализовать VAR-модели, обучать их и строить прогнозы в MetaTrader 5 в реальном времени, чтобы анализировать взаимозависимые движения валютных курсов для получения лучших результатов в трейдинге.
Нейросети в трейдинге: Оптимизация Cross-Attention для анализа длинных последовательностей рынка (Основные компоненты) Нейросети в трейдинге: Оптимизация Cross-Attention для анализа длинных последовательностей рынка (Основные компоненты)
В статье продолжается реализация фреймворка STCA средствами MQL5. Оригинальные оптимизации Self-Attention перенесены в архитектуру FlashAttention-2 и адаптированы под финансовые данные. Особое внимание уделено аккумулированию и распределению градиентов между потоками рабочей группы для анализа длинных временных рядов и многоголового внимания.