preview
Оптимизатор ястребов Харриса — Harris Hawks Optimization (HHO)

Оптимизатор ястребов Харриса — Harris Hawks Optimization (HHO)

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

Содержание

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


Введение

При оптимизации параметров торговой системы в ограниченном бюджете вычислений (попыток оценки) вы неизбежно сталкиваетесь с дилеммой: либо алгоритм слишком рано «залипает» в первом найденном локальном оптимуме, либо он длительно исследует пространство и к концу итерационного бюджета не успевает «дожать» перспективные зоны. На практике момент переключения между разведкой (exploration) и доработкой (exploitation) обычно задаётся вручную через статичные гиперпараметры — и этот выбор сильно зависит от конкретной задачи, размерности и сетки дискретизации. В условиях ограниченного числа вызовов функции качества и правила «одна оценка фитнеса за итерацию» нужен механизм, который сам переводит поиск от широкого охвата к точной атаке по мере расходования бюджета, без ручной подстройки порогов.

Harris Hawks Optimization (HHO) предлагает именно такой подход: в основе лежит скалярная «энергия побега» E, детерминированно убывающая от ±2 до 0 по мере прогрессирования итераций, которая автоматом переключает алгоритм между фазами исследования и атаки. В статье мы не ограничились биологической метафорой: мы воспроизвели все режимы HHO (два варианта исследования, четыре стратегии атаки, включая пикирование по закону Леви), адаптировали их под интерфейс унифицированного тестового стенда MQL5 с требованием одной оценки фитнеса на итерацию и реализовали рабочий класс C_AO_HHO. При этом техническое ограничение стенда (невозможность делать два вызова функции качества в одной итерации) вынудило в стратегии с Леви заменить прямую проверку F(Y)<F(X) прокси‑правилом на основе истории улучшений — деталь, важная для воспроизводимости и интерпретации результатов.

Цель статьи — предоставить готовый компонент (Init/Moving/Revision, параметры popSize и β), воспроизводимый протокол тестирования (3 функции: Hilly, Forest, Megacity; три уровня размерности; 10000 вызовов на прогон; 10 повторов) и честную оценку применимости HHO к практическим задачам — в частности, к калибровке торговых систем в условиях ограниченного бюджета.


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

В техасских пустынях существует единственный вид хищных птиц, охотящихся стаей — ястреб Харриса. Несколько птиц расходятся веером, прочёсывая широкую территорию в поисках кролика. Как только добыча обнаружена, тактика мгновенно меняется — разведчики превращаются в слаженную команду, которая сжимает кольцо, перекрывая жертве все пути отступления. Именно эту охоту воспроизводит алгоритм оптимизации ястребов Харриса Harris Hawks Optimization, (HHO), предложенный Heidari et al. в 2019 году.

Каждый ястреб — отдельный кандидат на решение задачи оптимизации, точка X_i в D-мерном пространстве поиска. «Кролик» X_prey — лучшее решение, найденное к текущему моменту, его роль исполняет вектор cB[] глобального рекорда. Алгоритм управляет тем, как ястребы двигаются — поодиночке изучая пространство или сообща сжимаясь вокруг находки. В реализации следующая позиция каждого агента накапливается в вектор X_next (cP[]), а затем привязывается к сетке поиска и передаётся фитнес-функции.

Ключевое новшество HHO — энергия побега E, которая убывает от ±2 к нулю по мере исчерпания итераций (Eq. 3):

E = 2 · E₀ · (1 − t / T)

E₀ ~ U(−1, 1) — случайная начальная энергия, независимая для каждого агента и каждой итерации; t — номер текущей итерации; T — общее число итераций. Знак E₀ задаёт направление «борьбы» кролика за выживание, |E| определяет фазу алгоритма.

Параметр E работает как таймер, автоматически переключающий алгоритм между двумя режимами. Пока |E| ≥ 1 — ястребы ведут разведку, разлетаясь по всему пространству и действуя независимо. Когда |E| опускается ниже 1, поиск сужается и начинается охота. Аналогия: пока до конца аукциона далеко, участники изучают самые разные лоты по всему залу; когда таймер приближается к нулю — все сосредоточены на одном лоте.

В режиме разведки (|E| ≥ 1) каждый ястреб выбирает, куда сесть, двумя способами в зависимости от случайного параметра q ~ U(0, 1). При q < 0.5 ориентиром служит случайный сосед X_rand из текущей популяции (Eq. 1a):

X_next[i] = X[rand] − r1 · |X[rand] − 2 · r2 · X[i]|;

где r1, r2 ~ U(0, 1) — независимые для каждой координаты, X[rand] — позиция случайно выбранного агента стаи. При q ≥ 0.5 ястреб использует разность между позицией кролика и средним положением стаи X_mean (Eq. 2):

X_mean = (1/N) · Σ X[i] 

и смещается в произвольное место на основе этой разности (Eq. 1b):

X_next[i] = (X_prey − X_mean) − r3 · (LB + r4 · (UB − LB)); r3, r4 ~ U(0, 1);

LB, UB — нижняя и верхняя границы поиска. X_mean вычисляется один раз как снапшот начала итерации — все агенты видят одинаковое среднее, что исключает зависимость от порядка обновления. Оба варианта обеспечивают широкий разброс агентов, снижающий риск застрять в локальном оптимуме.

Когда кролик обнаружен (|E| < 1), в дело вступают четыре стратегии атаки. Их выбор определяется двумя вопросами: насколько кролик истощён и удалось ли ему вырваться. Истощённость определяет |E|: при |E| ≥ 0.5 кролик ещё бодр, при |E| < 0.5 — силы на исходе. Успех побега определяет случайный параметр r ~ U(0, 1), независимый от E.

Мягкая осада (r ≥ 0.5, |E| ≥ 0.5) — ястребы плавно сокращают дистанцию, как пожарные, методично вытесняющие огонь (Eq. 4):

X_next[i] = (X_prey − X[i]) − E · |J · X_prey − X[i]|

J = 2 · (1 − U(0,1)) — сила случайного прыжка кролика в попытке вырваться; разность X_prey − X[i] — вектор сближения. Жёсткая осада (r ≥ 0.5, |E| < 0.5) — кролик истощён, прямой бросок без обходных манёвров (Eq. 6):

X_next[i] = X_prey − E · |X_prey − X[i]|

J в этой стратегии не используется — кролик уже не способен на активный манёвр.

Полёт Леви — это не совсем обычное случайное блуждание. Большинство шагов маленькие, но изредка случается скачок в десятки раз длиннее. Такое распределение встречается в природе повсюду — от маршрутов альбатросов до движений молекул. Шаг Леви вычисляется по формуле Mantegna (Eq. 9):

LF = u / |v|^(1/β),   u ~ N(0, σ²),   v ~ N(0, 1)

σ = [Γ(1+β)·sin(πβ/2) / (Γ((1+β)/2)·β·2^((β−1)/2))]^(1/β); где β = 1.5 — показатель хвоста распределения, константа, u и v — независимые нормальные сэмплы, генерируются методом БоксаМюллера, σ предвычисляется однократно в Init(). Результат LF знаковый: u может быть отрицательным, что обеспечивает двунаправленность прыжка.

В HHO полёт Леви применяется в стратегиях 3 и 4 как запасной манёвр: если направленный шаг к кролику не улучшает позицию ястреба, добавляется случайное Леви-возмущение. Мягкая осада с пикированием (r < 0.5, |E| ≥ 0.5) — кролик ещё бодр и петляет; ястреб сначала пробует направленный шаг Y, при неудаче добавляет Леви (Eq. 7–10):

Y = X_prey − E · |J · X_prey − X[i]|

Z = Y + U(0,1) · LF(D)

X_next[i] = Y  если F(Y) < F(X[i]),  иначе  Z.

Жёсткая осада с пикированием (r < 0.5, |E| < 0.5) — кролик истощён; атака ориентируется на центр масс стаи X_mean, а не на индивидуальную позицию агента (Eq. 11–13):

Y = X_prey − E · |J · X_prey − X_mean|

Z = Y + U(0,1) · LF(D)

X_next[i] = Y  если F(Y) < F(X[i]),  иначе  Z.

Выбор Y или Z требует двух вызовов фитнес-функции внутри одной итерации. В данной реализации фреймворк допускает ровно одну оценку на итерацию, поэтому применяется прокси-критерий: если агент улучшился в прошлой итерации (f[i] > f_prev[i]) — используется Y; иначе — Z. На первой итерации f_prev[i] = −∞, что гарантирует выбор Z и стимулирует начальное разнообразие через Леви-прыжки.

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

HHO_algorithm

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

Иллюстрация содержит все ключевые элементы алгоритма. Левая панель показывает фазу исследования: 9 ястребов разлетаются в случайных направлениях по всему пространству, кролик присутствует, но не является фокусом внимания. Правая панель показывает фазу эксплуатации: 4 ястреба атакуют кролика каждый по своей стратегии — прямые синие стрелки для стратегий 1 и 2 (мягкая и жёсткая осада), фиолетовые зигзаги для стратегий 3 и 4 (осада + пикирование Леви, длинный прыжок в конце пути намеренно выделен). Временная шкала внизу показывает убывание E от 2 до 0 с кривой затухания и чёткой границей |E| = 1.

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

Init
INIT(LB[], UB[], step[], epochsP):
t ← 0 ; T ← epochsP
// Eq.(9): σ — предвычисляется однократно через LGamma()
num ← exp(LGamma(1+β)) · sin(π·β/2)
den ← exp(LGamma((1+β)/2)) · β · 2(β−1)/2
σ ← (den > 1e-15) ? (num/den)1/β : 1 // σ для LevyStep()
for i = 0..N−1:
for c = 0..D−1: X_next[i][c] ← U(LB[c], UB[c]) // случайная инициализация
f_prev[i] ← −∞ // → первая итерация всегда выбирает Z (Lévy)
Moving · вызывается перед каждым расчётом фитнеса
MOVING():
for i = 0..N−1:
for c = 0..D−1: X[i][c] ← snap(X_next[i][c], LB[c], UB[c], step[c])
Revision · вызывается после расчёта f[i] = F(X[i]) внешним кодом
REVISION():
t ← t + 1
// ─── 1. Обновление личных и глобального рекордов ─────────────
for i = 0..N−1:
if f[i] > f_best[i]: f_best[i] ← f[i] ; X_best[i] ← X[i]
if f[i] > f_prey: f_prey ← f[i] ; X_prey ← X[i] // «кролик»
// ─── 2. Среднее положение стаи по X[i] (снапшот) ── Eq.(2) ──
for c = 0..D−1: X_mean[c] ← (1/N) · Σᵢ X[i][c]
// ─── 3. Общий энергетический множитель итерации ──────────────
E1 ← 2 · (1 − t / T)
// ─── 4. Обновление позиции каждого ястреба ───────────────────
for i = 0..N−1:
// [L3] прокси «Y лучше X» — фиксируем ДО обновления f_prev
improvedLast ← (f[i] > f_prev[i])
f_prev[i] ← f[i]
// Eq.(3): E = E1 · E0, E0 ~ U(−1, 1) — inline, отдельная переменная не нужна
E ← E1 · (2 · U(0,1) − 1)
|E| ← |E|
if |E| ≥ 1: // ══ ИССЛЕДОВАНИЕ ══
q ← U(0,1) // генерируется только в этой ветке
if q < 0.5: // Eq.(1a): случайное дерево — ориентация на X[ri]
ri ← randInt(0, N−1)
r1, r2 ← U(0,1)
for c = 0..D−1:
X_next[i][c] ← clamp( X[ri][c] − r1·|X[ri][c] − 2·r2·X[i][c]|, LB[c], UB[c] )
else: // q ≥ 0.5 — Eq.(1b): ориентация от X_prey через X_mean
r3, r4 ← U(0,1)
for c = 0..D−1:
X_next[i][c] ← clamp( (X_prey[c] − X_mean[c]) − r3·(LB[c] + r4·(UB[c]−LB[c])), LB[c], UB[c] )
else if |E| < 1 and r ≥ 0.5 and |E| ≥ 0.5: // ── Стратегия 1: мягкая осада ──
r ← U(0,1) // генерируется первым в блоке эксплуатации
J ← 2·(1 − U(0,1)) // Jump_strength
for c = 0..D−1: // Eq.(4), ΔX = X_prey − X[i] (Eq.5 inline)
X_next[i][c] ← clamp( (X_prey[c]−X[i][c]) − E·|J·X_prey[c] − X[i][c]|, LB[c], UB[c] )
else if |E| < 1 and r ≥ 0.5 and |E| < 0.5: // ── Стратегия 2: жёсткая осада ──
// J не нужен в этой стратегии
for c = 0..D−1: // Eq.(6)
X_next[i][c] ← clamp( X_prey[c] − E·|X_prey[c] − X[i][c]|, LB[c], UB[c] )
else if |E| < 1 and r < 0.5 and |E| ≥ 0.5: // ── Стратегия 3: мягкая осада + Lévy ──
J ← 2·(1 − U(0,1))
for c = 0..D−1: // Eq.(7) — кандидат Y
Y[c] ← clamp( X_prey[c] − E·|J·X_prey[c] − X[i][c]|, LB[c], UB[c] )
if improvedLast: // прокси F(Y) < F(X[i]) → берём Y [L3]
X_next[i] ← Y
else: // прокси F(Y) ≥ F(X[i]) → Z = Y + Lévy Eq.(8)
for c = 0..D−1:
Z[c] ← clamp( Y[c] + U(0,1) · LevyStep(), LB[c], UB[c] ) // rand·LF(D)
X_next[i] ← Z
else: // |E| < 1, r < 0.5, |E| < 0.5 ── Стратегия 4: жёсткая осада + Lévy ──
J ← 2·(1 − U(0,1))
for c = 0..D−1: // Eq.(12) — кандидат Y' (X_mean вместо X[i])
Y[c] ← clamp( X_prey[c] − E·|J·X_prey[c] − X_mean[c]|, LB[c], UB[c] )
if improvedLast: // прокси F(Y') < F(X[i]) → берём Y' [L3]
X_next[i] ← Y
else: // прокси F(Y') ≥ F(X[i]) → Z' = Y' + Lévy Eq.(13)
for c = 0..D−1:
Z[c] ← clamp( Y[c] + U(0,1) · LevyStep(), LB[c], UB[c] )
X_next[i] ← Z
// end strategy selection
// end for i
return X_prey, f_prey
LevyStep · знаковый скалярный шаг — вызывается per-coordinate
LevyStep() → double:
// Box-Muller: два независимых N(0,1) из одной пары равномерных чисел
r1 ← U(1e-15, 1), r2 ← U(0, 1) // r1 > 0 чтобы ln(r1) не взорвался
n1 ← √(−2·ln r1) · cos(2π·r2) // n1 ~ N(0, 1)
n2 ← √(−2·ln r1) · sin(2π·r2) // n2 ~ N(0, 1)
u ← n1 · σ // u ~ N(0, σ²), как randn()*sigma в оригинале
v ← n2 // v ~ N(0, 1), как randn() в оригинале
av ← max(|v|, 1e-15)
return u / av1/β // знаковый результат — сохраняет знак u, обеспечивает ±направление

Теперь можем перейти непосредственно к реализации.

Класс C_AO_HHO наследует базовый C_AO, предоставляющий унифицированный интерфейс тестового стенда: массив агентов a[], глобальный лучший вектор cB[], счётчики coords и popSize, вспомогательный объект "u" с генераторами случайных чисел. Среди приватных полей класса "t" и "T" хранят текущую итерацию и её максимум — они необходимы для вычисления убывающей энергии E1 = 2·(1 − t/T). Поле sigma вычисляется один раз в Init() и используется функцией LevyStep() на протяжении всего прогона. Временные буферы Xm[] и Ytmp[] размечаются под coords элементов при инициализации и переиспользуются без повторного выделения памяти.

//────────────────────────────────────────────────────────────────────
class C_AO_HHO : public C_AO
{
  public: //------------------------------------------------------------
  ~C_AO_HHO () { }
  C_AO_HHO ()
  {
    ao_name = "HHO";
    ao_desc = "Harris Hawks Optimization";
    ao_link = "https://www.mql5.com/ru/articles/21898";

    popSize = 50;
    beta    = 1.5;

    ArrayResize (params, 2);
    params [0].name = "popSize"; params [0].val = popSize;
    params [1].name = "beta";    params [1].val = beta;
  }

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

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  double beta; // показатель хвоста Леви (β = 1.5 в оригинале)

  private: //---------------------------------------------------------
  int    t;      // текущая итерация (счёт с 1)
  int    T;      // всего итераций
  double sigma;  // σ для формулы Леви (Eq. 9), вычисляется один раз в Init

  double Xm   []; // [coords]  среднее положение стаи (Eq. 2)
  double Ytmp []; // [coords]  кандидат Y  (стратегии 3, 4)

  double LGamma   (double x); // логарифм Г(x), аппроксимация Ланцоша
  double LevyStep ();         // [L2] скалярный шаг Леви: u~N(0,σ), v~N(0,1)

  double Clamp (double v, double lo, double hi)
  {
    return v < lo ? lo : (v > hi ? hi : v);
  }
};
//————————————————————————————————————————————————————————————————————

Метод Init() выполняет полный сброс состояния алгоритма перед каждым тестовым прогоном. Он вызывает StandardInit(), который размечает массив агентов, копирует границы диапазона и сбрасывает генератор случайных чисел. После этого сохраняет epochsP как "T", обнуляет счётчик "t" и размечает буферы. Предвычисление sigma выполняется по формуле Мантеньи (Eq. 9): числитель — Γ(1+β)·sin(πβ/2), знаменатель — Γ((1+β)/2)·β·2^((β-1)/2); результат возводится в степень 1/β. Вычисление делегировано LGamma() во избежание переполнения при прямом вычислении гамма-функции через факториал. Каждый агент инициализируется равномерно в [LB, UB], а поле a[i].fP сбрасывается в −DBL_MAX — это гарантирует, что на первой итерации improvedLast = false и стратегии 3 и 4 всегда выберут ветку с Lévy-прыжком, стимулируя начальное разнообразие.

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

  //------------------------------------------------------------------
  T = (epochsP > 0) ? epochsP : 1;
  t = 0;

  ArrayResize (Xm,   coords);
  ArrayResize (Ytmp, coords);

  //--- Eq. (9): σ = [Г(1+β)·sin(πβ/2) / (Г((1+β)/2)·β·2^((β-1)/2))]^(1/β)
  double num = exp (LGamma (1.0 + beta)) * sin (M_PI * beta * 0.5);
  double den = exp (LGamma ((1.0 + beta) * 0.5)) * beta * pow (2.0, (beta - 1.0) * 0.5);
  sigma = (den > 1e-15) ? pow (fabs (num / den), 1.0 / beta) : 1.0;

  //--- начальные позиции: равномерно случайно в [LB, UB]
  for (int i = 0; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      a [i].cP [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
    }
    a [i].fP = -DBL_MAX; // сброс истории улучшений для [L3]
  }

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

Метод Moving() переносит спроецированные позиции из cP[] в c[] с привязкой к дискретной сетке через SeInDiSp(). Вызывается тестовым стендом перед каждым расчётом фитнес-функции. Вся логика вычисления следующего шага сосредоточена в Revision(); Moving() — исключительно операция проекции.

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

Метод Revision() является основным и вызывается тестовым стендом после того, как внешний код рассчитал фитнес всех агентов и записал результаты в a[i].f. Он выполняет четыре последовательных шага. Первый — обновление лучших позиций: для каждого агента сравнивается текущий фитнес с личным рекордом a[i].fB и с глобальным fB, при улучшении копируются соответствующие координатные векторы. Вектор cB[] всегда хранит позицию кролика. Второй шаг — вычисление среднего положения стаи Xm[] (Eq. 2) по текущим координатам a[i].c[]. Третий шаг — вычисление E1 = 2·(1 − t/T), общего для всей итерации убывающего множителя. Четвёртый шаг — обновление позиции каждого ястреба.

В начале блока фиксируется прокси-признак improvedLast = (a[i].f > a[i].fP), после чего fP обновляется для следующей итерации. Этот признак заменяет двойной вызов фитнес-функции, применяемый в оригинале в стратегиях 3 и 4: если агент улучшился в прошлой итерации — доверяем направленному шагу Y; если нет — добавляем Lévy-возмущение Z. Случайные числа E, q или r и при необходимости J генерируются строго в той ветке кода, где они нужны, без лишних вызовов генератора.

Вспомогательный метод Clamp() ограничивает значение v отрезком [lo, hi] и применяется после каждого вычисления новой позиции агента, гарантируя, что кандидатный вектор cP[] остаётся в допустимом диапазоне поиска независимо от величины шага — включая потенциально большие Lévy-прыжки.

//————————————————————————————————————————————————————————————————————
// Revision(): обновляет X_prey, X_m и вычисляет cP для следующего шага.
//
// Структура соответствует оригиналу:
//   |E| >= 1 → исследование:
//       q < 0.5  → X_rand formula (Eq. 1a)      [L1]
//       q >= 0.5 → X_prey − X_m  (Eq. 1b)
//   |E| <  1 → эксплуатация:
//       r>=0.5, |E|>=0.5 → стратегия 1: мягкая осада          Eq. (4-5)
//       r>=0.5, |E|< 0.5 → стратегия 2: жёсткая осада         Eq. (6)
//       r< 0.5, |E|>=0.5 → стратегия 3: мягкая осада + Lévy  Eq. (7-10)
//       r< 0.5, |E|< 0.5 → стратегия 4: жёсткая осада + Lévy Eq. (11-13)
//
// [L3] В оригинале стратегии 3/4 делают два вызова fobj (для Y и Z).
//      Прокси в нашем фреймворке: если агент улучшился в прошлой итерации
//      (a[i].f > a[i].fP) — доверяем направленному шагу Y;
//      иначе добавляем Lévy → Z (как запасной вариант).
void C_AO_HHO::Revision ()
{
  t++;

  //─── 1. Обновление личных и глобального лучших ──────────────────
  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f > a [i].fB)
    {
      a [i].fB = a [i].f;
      ArrayCopy (a [i].cB, a [i].c, 0, 0, coords);
    }
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy (cB, a [i].c, 0, 0, coords);
    }
  }

  //─── 2. Среднее положение стаи Xm (Eq. 2) ───────────────────────
  ArrayInitialize (Xm, 0.0);
  for (int i = 0; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      Xm [c] += a [i].c [c];
    }
  }
  for (int c = 0; c < coords; c++)
  {
    Xm [c] /= (double)popSize;
  }

  //─── 3. E1 — общий для всей итерации  ─────────────
  double E1 = 2.0 * (1.0 - (double)t / (double)T);

  //─── 4. Обновление позиции каждого ястреба ──────────────────────
  for (int i = 0; i < popSize; i++)
  {
    //--- [L3] Фиксируем признак улучшения ДО обновления fP
    bool improvedLast = (a [i].f > a [i].fP);
    a [i].fP = a [i].f; // сохраняем для следующей итерации

    //--- Eq. (3): E = E1·E0, E0 ~ U(−1,1)  [E0 не нужен отдельно]
    double E    = E1 * (2.0 * u.RNDfromCI (0.0, 1.0) - 1.0);
    double absE = fabs (E);

    if (absE >= 1.0)
    {
      //══ ИССЛЕДОВАНИЕ ══════════════════════════════════════════════
      // q генерируется только здесь
      double q = u.RNDfromCI (0.0, 1.0);

      if (q < 0.5)
      {
        //─ [L1] Eq. (1a): ориентация на случайного члена стаи ──────
        //  Xi(t+1) = X_rand − r1·|X_rand − 2·r2·Xi|
        int    ri = u.RNDminusOne (popSize);
        double r1 = u.RNDfromCI (0.0, 1.0);
        double r2 = u.RNDfromCI (0.0, 1.0);
        for (int c = 0; c < coords; c++)
        {
          a [i].cP [c] = Clamp (a [ri].c [c] - r1 * fabs (a [ri].c [c] - 2.0 * r2 * a [i].c [c]), rangeMin [c], rangeMax [c]);
        }
      }
      else
      {
        //─ Eq. (1b): ориентация на разность X_prey − X_m ───────────
        //  Xi(t+1) = (X_prey − X_m) − r3·((UB−LB)·r4 + LB)
        double r3 = u.RNDfromCI (0.0, 1.0);
        double r4 = u.RNDfromCI (0.0, 1.0);
        for (int c = 0; c < coords; c++)
        {
          a [i].cP [c] = Clamp ((cB [c] - Xm [c]) - r3 * (rangeMin [c] + r4 * (rangeMax [c] - rangeMin [c])), rangeMin [c], rangeMax [c]);
        }
      }
    }
    else
    {
      //══ ЭКСПЛУАТАЦИЯ ══════════════════════════════════════════════
      // r генерируется только здесь
      double r = u.RNDfromCI (0.0, 1.0);

      if (r >= 0.5 && absE >= 0.5)
      {
        //─ Стратегия 1: мягкая осада ─────────────────────────────────
        //  ΔX = X_prey − Xi                                   Eq. (5)
        //  Xi(t+1) = ΔX − E·|J·X_prey − Xi|                  Eq. (4)
        //  J генерируется здесь (Jump_strength)
        double J = 2.0 * (1.0 - u.RNDfromCI (0.0, 1.0));
        for (int c = 0; c < coords; c++)
        {
          a [i].cP [c] = Clamp ((cB [c] - a [i].c [c]) - E * fabs (J * cB [c] - a [i].c [c]), rangeMin [c], rangeMax [c]);
        }
      }
      else
      {
        if (r >= 0.5 && absE < 0.5)
        {
          //─ Стратегия 2: жёсткая осада ────────────────────────────────
          //  Xi(t+1) = X_prey − E·|X_prey − Xi|                Eq. (6)
          //  J не используется в этой стратегии
          for (int c = 0; c < coords; c++)
          {
            a [i].cP [c] = Clamp (cB [c] - E * fabs (cB [c] - a [i].c [c]), rangeMin [c], rangeMax [c]);
          }
        }
        else
        {
          if (r < 0.5 && absE >= 0.5)
          {
            //─ Стратегия 3: мягкая осада + пикирование Леви ──────────────
            //  Y = X_prey − E·|J·X_prey − Xi|                    Eq. (7)
            //  Z = Y + rand·LF(D)                                 Eq. (8)
            //  Xi = Y если F(Y)<F(Xi), иначе Z если F(Z)<F(Xi),
            //          иначе Xi остаётся (stay-put — фреймворк не поддерживает).
            //  [L3] прокси: improvedLast → Y; иначе → Z.
            //  J генерируется здесь
            double J = 2.0 * (1.0 - u.RNDfromCI (0.0, 1.0));
            for (int c = 0; c < coords; c++)
            {
              Ytmp [c] = Clamp (cB [c] - E * fabs (J * cB [c] - a [i].c [c]), rangeMin [c], rangeMax [c]);
            }

            if (improvedLast)
            {
              for (int c = 0; c < coords; c++) a [i].cP [c] = Ytmp [c];
            }
            else
            {
              // Z = Y + rand·LF(D), rand(1,dim).*Levy(dim)  [L2]
              for (int c = 0; c < coords; c++)
              {
                a [i].cP [c] = Clamp (Ytmp [c] + u.RNDfromCI (0.0, 1.0) * LevyStep (), rangeMin [c], rangeMax [c]);
              }
            }
          }
          else   // r < 0.5 && absE < 0.5
          {
            //─ Стратегия 4: жёсткая осада + пикирование Леви ─────────────
            //  Y' = X_prey − E·|J·X_prey − X_m|                  Eq. (12)
            //  Z' = Y' + rand·LF(D)                               Eq. (13)
            //  Xi = Y' если F(Y')<F(Xi), иначе Z' если F(Z')<F(Xi),
            //          иначе Xi остаётся (stay-put — фреймворк не поддерживает).
            //  [L3] прокси: improvedLast → Y'; иначе → Z'.
            //  J генерируется здесь
            double J = 2.0 * (1.0 - u.RNDfromCI (0.0, 1.0));
            for (int c = 0; c < coords; c++)
            {
              Ytmp [c] = Clamp (cB [c] - E * fabs (J * cB [c] - Xm [c]), rangeMin [c], rangeMax [c]);
            }

            if (improvedLast)
            {
              for (int c = 0; c < coords; c++)
              {
                a [i].cP [c] = Ytmp [c];
              }
            }
            else
            {
              // Z' = Y' + rand·LF(D)  [L2]
              for (int c = 0; c < coords; c++)
              {
                a [i].cP [c] = Clamp (Ytmp [c] + u.RNDfromCI (0.0, 1.0) * LevyStep (), rangeMin [c], rangeMax [c]);
              }
            }
          }
        }
      }
    }
  } // for i
}
//————————————————————————————————————————————————————————————————————

Метод LevyStep() возвращает один скалярный шаг полёта Леви по формуле Мантеньи. Два независимых нормальных сэмпла n1 и n2 генерируются методом БоксаМюллера из двух равномерных чисел r1 ∈ (1e-15, 1) и r2 ∈ [0, 1). Нижняя граница r1 исключает нулевой аргумент у логарифма. Шаг вычисляется как uu / |vv|^(1/β), где uu = n1·sigma, vv = n2. Результат знаковый — n1 может быть отрицательным, что обеспечивает двунаправленность прыжка. Вызывающий код умножает результат на независимый равномерный множитель u.RNDfromCI(0, 1) : rand(1,dim).*Levy(dim).

//————————————————————————————————————————————————————————————————————
// [L2] Скалярный шаг Леви:
//   u = randn()*σ,  v = randn()
//   step = u / |v|^(1/β)
// Нормальные сэмплы генерируются методом БоксаМюллера.
// Levy() возвращает вектор — здесь скаляр; вызывающий код
// вызывает функцию per-coordinate, rand(1,dim).*Levy(dim).
double C_AO_HHO::LevyStep ()
{
  // Box-Muller: два независимых N(0,1)
  double r1 = u.RNDfromCI (1e-15, 1.0);
  double r2 = u.RNDfromCI (0.0,   1.0);
  double n1 = sqrt (-2.0 * log (r1)) * MathCos (2.0 * M_PI * r2);
  double n2 = sqrt (-2.0 * log (r1)) * sin (2.0 * M_PI * r2);

  double uu = n1 * sigma; // u ~ N(0, sigma^2)
  double vv = n2;         // v ~ N(0, 1)
  double av = fabs (vv);
  if (av < 1e-15) av = 1e-15;
  return fabs (uu / pow (av, 1.0 / beta));
}
//————————————————————————————————————————————————————————————————————

Метод LGamma() вычисляет натуральный логарифм гамма-функции ln(Γ(x)) с точностью около 15 значащих цифр для x > 0 по алгоритму Ланцоша с параметрами g = 7, n = 8. Используется исключительно в Init() для вычисления sigma и не влияет на производительность основного цикла. Для x < 0.5 применяется формула отражения Γ(x)·Γ(1−x) = π/sin(πx), что обеспечивает корректную работу при дробных значениях beta, близких к нулю. Возврат логарифма, а не самого значения Γ(x), предотвращает переполнение double при больших аргументах.

//————————————————————————————————————————————————————————————————————
// Логарифм гамма-функции — аппроксимация Ланцоша (g=7, n=8).
// Точность ~15 значащих цифр для Re(x) > 0.
double C_AO_HHO::LGamma (double x)
{
  static const double cL [8] =
  {
    676.5203681218851,    -1259.1392167224028,
    771.32342877765313,   -176.61502916214059,
    12.507343278686905,    -0.13857109526572012,
    9.9843695780195716e-6,  1.5056327351493116e-7
  };
  if (x < 0.5) return log (M_PI / sin (M_PI * x)) - LGamma (1.0 - x);
  x -= 1.0;
  double ag = 0.99999999999980993;
  for (int k = 0; k < 8; k++) ag += cL [k] / (x + k + 1.0);
  double tt = x + 7.5;
  return 0.5 * log (2.0 * M_PI) + (x + 0.5) * log (tt) - tt + log (ag);
}
//————————————————————————————————————————————————————————————————————


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

Алгоритм тестировался на трёх стандартных функциях стенда — Hilly, Forest и Megacity — при трёх уровнях размерности: 10, 50 и 1000 координат соответственно, по 10000 вычислений на каждый прогон и 10 повторений. Общий результат впечатляющий, о нём мы ещё поговорим.

HHO|Harris Hawks Optimization|50.0|1.5|
=============================
5 Hilly's; Func runs: 10000; result: 0.5845287633538552
25 Hilly's; Func runs: 10000; result: 0.4063647569651291
500 Hilly's; Func runs: 10000; result: 0.34188339366081033
=============================
5 Forest's; Func runs: 10000; result: 0.9294946030062039
25 Forest's; Func runs: 10000; result: 0.960139366657175
500 Forest's; Func runs: 10000; result: 0.9512729220389199
=============================
5 Megacity's; Func runs: 10000; result: 0.6492307692307692
25 Megacity's; Func runs: 10000; result: 0.6255384615384616
500 Megacity's; Func runs: 10000; result: 0.7003076923076924
=============================
All score: 6.14876 (68.32%)

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

Hilly

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

Forest

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

Megacity

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

Skin

HHO на тестовой функции Skin

Ackley

HHO на тестовой функции Ackley

По итогам тестирования алгоритм занимает 1 место в рейтинге лучших алгоритмов оптимизации, несмотря на не самые лучшие показатели по функции Hilly.

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 HHO harris_hawks_optimization 0,58452 0,40636 0,34188 1,33276 0,92949 0,96013 0,95127 2,84089 0,64923 0,62553 0,70031 1,97507 6,149 68,32
2 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
3 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
4 AMOm animal migration ptimization 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
5 (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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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
21 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
22 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
23 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
24 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
25 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
26 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
27 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
28 CSO competitive_swarm_optimizer 0,90291 0,61887 0,29830 1,82008 0,99982 0,64581 0,23975 1,88538 0,63384 0,40553 0,12852 1,16789 4,873 54,15
29 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
30 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
31 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
32 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
33 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
34 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
35 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
36 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
37 (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
38 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
39 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
40 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
41 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
42 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
43 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
44 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
45 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
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


Выводы

С точки зрения реализации — результат положителен. Мы получили воспроизводимый, совместимый со стендом компонент C_AO_HHO (параметры popSize и β), корректную реализацию Леви‑шагов (метод Бокса‑Мюллера, предвычисленное σ) и адаптацию логики Y/Z под ограничение одной оценки фитнеса за итерацию. Компонент можно подключить к унифицированному тестовому стенду без правок, и сценарий проверяем: 3 функции, 3 размерности, 10000 вызовов, 10 повторов — все метрики и коды доступны.

Результаты тестов дают двойственное заключение. По агрегированному скору HHO занял первое место (68.32%), с выдающимися показателями на функции Forest во всех размерностях. Однако детальный разбор показал, что лидерство обусловлено не универсальной эффективностью, а частичной «совместимостью» механики алгоритма со структурой конкретного бенчмарка. На функции Hilly HHO показал производительность, близкую к случайному поиску: узкая «игла» глобального максимума требует накопления личного опыта агента (компонента личного рекорда), которого в классическом HHO нет — и диагонально‑скоррелированные шаги алгоритма туда, как правило, не попадают. Это указывает на риск «бенчмарк‑специфичности»: высокие места в рейтинге могут отражать совпадение траекторий алгоритма с геометрией тестовой функции, а не общую пригодность для всех классов задач.

Практические выводы для применения в оптимизации торговых систем:

  • HHO годится для задач с относительно широкими зонами притяжения и гладкой многомерной структурой — там его автоматическое переключение и Леви‑прыжки дают преимущество.
  • Для поиска узких, изолированных пиков (аналогов Hilly) HHO без дополнительных механизмов памяти или адаптации персональных рекордов уступает более ориентированным методам.
  • Всегда проверяйте алгоритм на наборе тестов, модифицированных под геометрию вашей задачи (сдвиги пиков, изменение ширины пиков, нарушение симметрии), чтобы исключить ложное ощущение надёжности, вызванное «совпадением» со стандартным бенчмарком.

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

tab

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

chart

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


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

Плюсы:

  1. Хорошие показатели на высоких размерностях задач на функции Forest.

Минусы:

  1. Слабая сходимость на функции Hilly.
  2. Вычислительная сложность.

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



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

# Имя Тип Описание
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 Testing AOs.mq5
Скрипт Единый испытательный стенд для всех популяционных алгоритмов оптимизации
9 Simple use of population optimization algorithms.mq5
Скрипт
Простой пример использования популяционных алгоритмов оптимизации без визуализации
10 Test_HHO.mq5
Скрипт Испытательный стенд для HHO
Прикрепленные файлы |
HHO.zip (445.47 KB)
Нейросети в трейдинге: Многодоменная архитектура анализа финансовых данных (Основные компоненты) Нейросети в трейдинге: Многодоменная архитектура анализа финансовых данных (Основные компоненты)
В статье продолжается перенос подходов фреймворка MDL в область решения задач финансовых рынков. Рассмотрены модули унифицированной токенизации разнородных данных, доменно-ориентированного внимание и Feature Self-Iteration, позволяющий эффективнее работать с историей признаков. Особое внимание уделено архитектурным решениям, снижающим вычислительную нагрузку и сохраняющим рыночный контекст в процессе анализа.
Оптимизация и форвард-анализ стратегий (Часть 1): Метод Пардо — базовая модель Оптимизация и форвард-анализ стратегий (Часть 1): Метод Пардо — базовая модель
Статья показывает, как выстроить воспроизводимый процесс разработки и проверки торговых систем в MetaTrader 5: от формализации правил входа/выхода и риск‑менеджмента до пост‑оптимизационной валидации. В основу положен "Метод Пардо": разбиение истории на in‑sample/out‑of‑sample, форвард‑тестирование, мульти‑рынки/таймфреймы и выбор устойчивых "плато" параметров вместо единичных пиков. На примерах PardoSystem и советников PardoEA / Breakout_Bounce показан практический тест‑план для тестера стратегий MetaTrader 5.
Торговые инструменты на MQL5 (Часть 14): Прокручиваемый текстовый холст с пиксельной точностью, сглаживанием и закругленной полосой прокрутки Торговые инструменты на MQL5 (Часть 14): Прокручиваемый текстовый холст с пиксельной точностью, сглаживанием и закругленной полосой прокрутки
В этой статье мы улучшим ценовую панель на основе холста (canvas) в MQL5, добавляя прокручиваемую текстовую панель с пиксельной точностью для руководств по использованию, преодолевающую собственные ограничения на прокрутку за счет настраиваемого сглаживания и округлого дизайна полосы прокрутки с функцией расширения при наведении курсора. Текстовая панель поддерживает фоны темы оформления с непрозрачностью, динамический перенос строк для содержимого, такого как инструкции и контакты, и интерактивную навигацию с помощью кнопок вверх / вниз, перетаскивания ползунка и прокрутки колесика мыши в области основного текста.
Создание самооптимизирующихся советников на MQL5 (Часть 8): Анализ нескольких стратегий (2) Создание самооптимизирующихся советников на MQL5 (Часть 8): Анализ нескольких стратегий (2)
Присоединяйтесь к нашему продолжению обсуждения, в котором мы объединим наши первые две торговые стратегии в ансамблевую торговую стратегию. Мы продемонстрируем различные возможные схемы комбинирования нескольких стратегий, а также способы управления пространством параметров, чтобы обеспечить возможность эффективной оптимизации даже при увеличении количества параметров.