preview
Тестовые чемпионы против реальных задач оптимизации

Тестовые чемпионы против реальных задач оптимизации

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

Содержание

  1. Введение
  2. Требования к тестовым функциям (бенчмаркам)
  3. Модификация тестовых функций с целью отсева пустышек
  4. Выводы


Введение

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

В статье мы формализуем этот риск (особенно — «диагональный эффект»), даём измеримый критерий диагностики и показываем, как убрать структурные преимущества из набора бенчмарков. После прочтения вы получите не абстрактный диагноз, а конкретные инструменты: правило проверки положения экстремума, процедуру верификации raw‑экстремумов (VerifyExtremes.mq5) и пример практической коррекции тестовых функций.

Алгоритм спиральной динамики SDO

В статье об алгоритме SDO был подробно разобран следующий механизм. Оригинальный алгоритм SDO, предложенный Тамурой и Ясудой в 2011 году, при перемещении агентов в многомерном пространстве связывает координаты попарно: для каждой пары координат строится отдельная двумерная спираль вращения. Тестовые функции нашей серии в многомерном варианте устроены как многократно продублированные двумерные функции — именно с такой же попарной структурой. Уже при разборе алгоритма в статье был явно зафиксирован вывод: «Поскольку в статьях используются многомерные функции в виде многократно продублированных двумерных, то оригинальный SDO может показать неоправданно высокие результаты, ведь в нём используется связывание координат попарно. Таким образом на других многомерных задачах, где координаты никак не связаны между собой, спиральный алгоритм покажет низкие результаты. То есть, для спирального алгоритма в данном примере на продублированных функциях неумышленно будут созданы «тепличные» условия.

Решение, принятое тогда, — замена попарного спирального вращения на проекцию двумерной спирали на одну координатную ось. В модифицированной версии SDOm каждая координата обновлялась независимо по формуле затухающих гармонических колебаний x(t) = A·e^(−γt)·cos(ωt + φ), что полностью устраняло межкоординатные связи и ликвидировало «тепличные» условия. Алгоритм стал честным, но результат, разумеется, понизился — SDOm занял 8-е место из 23 алгоритмов.

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

Модифицированный алгоритм оптимизации динго DOAm 

В статье о модификации DOA DOAm продемонстрировал характерную асимметрию результатов: выдающийся балл на функции Forest — и значительно более слабый результат на функции Hilly. Такой разрыв между результатами на двух функциях одного и того же стенда является надёжным маркером: алгоритм может не обладать универсальной оптимизационной силой, а структурно совпадать с одной конкретной тестовой функцией. Механизм совпадения в случае DOAm может быть иной, чем у SDO, но паттерн тот же — и именно он привлёк наше внимание к необходимости более глубокого анализа. 

После прочтения вы будете точно знать, как отличить среди алгоритмов оптимизации настоящего «сыщика» от красивой пустышки.


Требования к тестовым функциям (бенчмаркам)

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

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

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

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

Четвёртое условие: среднее значение поверхности должно быть существенно ниже 50% диапазона [min; max]. Иначе (особенно при большом числе переменных) случайные координаты могут давать результат около 50% от максимума или выше.

Три условия были точно выполнены при проектировании тестовых функций серии — Hilly, Forest и Megacity (третье условие практически выполнено, центр диапазона поиска очень близок к значению глобального минимума). Однако, как показала практика тестирования алгоритмов, этот список оказался неполным. Существует ещё одна структурная особенность, которая позволяет определённым алгоритмам систематически получать незаслуженно высокие результаты.

Почему экстремум не должен быть в центре диапазона?

Этот вопрос кажется очевидным, но механизм, стоящий за ним, стоит разобрать подробно, потому что именно по его образцу мы обнаружим и новый тип «читерства».

Существует целый класс алгоритмов, в которых координаты агентов тяготеют к среднему значению диапазона. Причины различны: (1) равномерная инициализация и последующее стягивание к центру масс; (2) усреднение позиций нескольких агентов; (3) симметричные приращения с нулевым математическим ожиданием, которые при достаточном числе итераций ведут к дрейфу к центру диапазона.

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

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

Но в чём состоит аналогия с проблемой, которую мы обнаружили позже? В том, что «центр диапазона» — это лишь одна из привилегированных геометрических структур. Диагональ пространства — другая. И точно так же, как экстремум в центре помогает алгоритмам с усреднением координат, экстремум на диагонали помогает алгоритмам с однородной генерацией значений координат.

Что такое диагональный эффект и почему он опасен?

Алгоритм HHO (Harris Hawks Optimization), предложенный в 2019 году и реализованный нами в предыдущей статье, стал тем случаем, который привёл к формулировке нового требования к тестовым функциям. Механизм «читерства» здесь не связан ни с попарным связыванием координат (как у SDO), ни с какой-то специфической поисковой стратегией — он фундаментальнее и затрагивает целый класс алгоритмов.

Ключевой элемент HHO — скалярная «энергия побега» E, убывающая от ±2 к нулю по мере расходования итерационного бюджета: E = 2·E₀·(1 − t/T), где E₀ ~ U(−1, 1) — случайная начальная энергия. Когда |E| ≥ 1, алгоритм работает в режиме разведки; при |E| < 1 переключается на фазу атаки, которая включает четыре стратегии. И вот здесь обнаруживается корень проблемы: в большинстве стратегий атаки множитель E применяется ко всем координатам одновременно как единый скалярный коэффициент.

Рассмотрим это на конкретных формулах из реализации. Стратегия 2 (жёсткая осада, r ≥ 0.5, |E| < 0.5) записывается как:

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

Здесь E — одно и то же число для всех координат. Это означает: если агент находится в точке (0.3, 0.5), а «кролик» (лучшее решение) — в точке (0.6, 0.8), то приращение по каждой координате пропорционально одной и той же величине E. Геометрически агент движется строго по прямой от своей текущей позиции к цели. Стратегия 1 (мягкая осада) использует формулу X_next[i] = (X_prey − X[i]) − E·|J·X_prey − X[i]|, где J — сила прыжка «кролика», но E по-прежнему одинаков для всех координат. Стратегии 3 и 4 добавляют полёт Леви, но только как запасной манёвр — основной кандидат Y вычисляется по тем же формулам с тем же скалярным E.

Есть и второй фактор, усиливающий проблему: в классическом HHO отсутствует компонента личного рекорда агента (pbest). Все агенты ориентируются на единый глобальный лучший результат — «кролика» (X_prey). Это означает, что вся стая движется к одной и той же точке, порождая пучок коррелированных диагональных траекторий. В статье о HHO мы прямо указали на это: «узкая "игла" глобального максимума требует накопления личного опыта агента (компонента личного рекорда), которого в классическом HHO нет — и диагонально-скоррелированные шаги алгоритма туда, как правило, не попадают».

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

Функция Forest в оригинальной форме имеет максимум в нормированных координатах x_norm ≈ 0.591, y_norm ≈ 0.731. Расстояние от этой точки до главной диагонали y = x составляет всего |0.731 − 0.591| = 0.140. Максимум лежит практически на диагонали. Коррелированные диагональные траектории HHO с высокой вероятностью пересекают область притяжения этого максимума: пучок агентов, стягивающийся к «кролику», неизбежно прочёсывает окрестность глобального оптимума. Результат — стабильно высокие показатели, практически не зависящие от размерности задачи: 0.929 при 5*2=10 переменных, 0.960 при 25*2=50 и 0.951 при 500*2=1000 — абсолютное первое место среди 45 алгоритмов на этой функции.

Картина на функции Hilly — зеркально противоположная. Максимум Hilly представляет собой узкую гауссову иглу шириной около 0.3 единицы, расположенную в нормированных координатах x_norm ≈ 0.253, y_norm ≈ 0.604. Разность |0.604 − 0.253| = 0.351 — максимум существенно удалён от диагонали. Диагональные траектории агентов HHO проходят мимо этого пика. Отсутствие личного рекорда (pbest) лишает алгоритм возможности «запомнить» случайные близкие подходы к игле и вернуться к ним. В результате HHO показывает 0.585, 0.406, 0.342 при 10, 50 и 1000 переменных соответственно — значения, почти неотличимые от случайного поиска (random walk показал 0.488, 0.322, 0.258 на той же функции). HHO занимает худшее место среди всех других алгоритмов таблицы на этой функции — и одновременно абсолютно первое на Forest.

Именно это контрастное сочетание является диагностическим признаком «читерства». Уже в статье о реализации HHO мы зафиксировали вывод: «лидерство обусловлено не универсальной эффективностью, а частичной "совместимостью" механики алгоритма со структурой конкретного бенчмарка». Итоговый балл 68.32% и формальное первое место в таблице сформированы не оптимизационными способностями, а структурным резонансом с одной функцией: Forest дала HHO 2.841 из 3.0 возможных баллов, а Hilly — лишь 1.333 из 3.0. Без аномального результата на Forest алгоритм оказался бы во второй половине таблицы.

Анализ трёх случаев — SDO с попарным связыванием координат, DOAm с асимметрией результатов Forest/Hilly и HHO с диагональным эффектом — приводит к общему выводу. Два существующих требования (экстремум не на границе и не в центре) необходимо дополнить ещё одним:

Глобальный экстремум должен быть удалён от главных диагоналей нормированного пространства поиска 

Конкретно: нормированные координаты x_norm и y_norm глобального максимума не должны быть близки друг к другу. Практический порог: |x_norm − y_norm| > 0.3.

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

Три требования теперь формируют замкнутую систему защиты от трёх основных классов структурных совпадений:

Экстремум не на границе — исключает преимущество алгоритмов с дефектом граничного контроля, координаты которых «переливаются» за край диапазона и случайно попадают в максимум.

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

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

Каждое из этих требований закрывает конкретный канал «читерства» — ситуацию, когда механика алгоритма резонирует со структурой тестовой функции, порождая иллюзию качественной оптимизации. Именно третьему условию подчинены все модификации функций Forest и Megacity, описанные далее.


Модификация тестовых функций

Цель модификации — не изменить характер функций, а убрать геометрическое преимущество, которое алгоритмы с диагональными траекториями получают бесплатно. Функции должны остаться столь же сложными для поиска, но перестать быть «удобными» для конкретной механики. Функция Hilly не менялась — её максимум уже достаточно удалён от диагонали (|x_norm − y_norm| = 0.351). Для двух оставшихся функций использованы два разных подхода: для Forest — сдвиг границ области определения (поискового окна) при неизменной формуле, для Megacity — изменение формулы при сохранении общей конструкции.

Forest

Оригинальная функция строится на базовом осциллирующем фоне a + b, поднятом в четвёртую степень, с добавлением двух гауссовых пиков — широкого (σ = 0.9) и узкого (σ = 0.3), и отрицательного гаусса, формирующего минимум. Формула Core() в модифицированной версии не изменилась — все коэффициенты, центры и сигмы гауссов остались прежними. Изменились границы области определения — поисковое окно сдвинуто:

Параметр Оригинал Модификация
xMinRange −43.50 −42.50
xMaxRange −39.00 −37.00
yMinRange −47.35 −45.00
yMaxRange −40.00 −39.80

За счёт расширения окна вправо (xMaxRange с −39 до −37) в область обзора попал иной максимум базовой осцилляции (a+b)⁴ с существенно бо́льшим сырым значением: raw_max = 3.027 вместо прежнего 1.878. Глобальный максимум переместился из точки (−40.84, −41.98) в точку (−37.70, −41.98). Нормированные координаты изменились: в оригинале x_norm ≈ 0.591, y_norm ≈ 0.731, Δ = 0.140 — максимум почти на диагонали.

В модифицированной версии x_norm ≈ 0.873, y_norm ≈ 0.580, Δ = 0.293 — максимум значительно удалён от диагонали, хотя и не достигает формального порога 0.3. Тем не менее, как покажут результаты тестирования, даже этого смещения достаточно для резкого падения результатов HHO — что подтверждает чувствительность диагональных алгоритмов к положению максимума.

Forest

Тестовая функция Forest

В таком виде функция Forest теперь включена в библиотеку тестовых функций.

//——————————————————————————————————————————————————————————————————————————————
class C_Forest : public C_Function
{
public: //===================================================================
  C_Forest ()
  {
    fuName = "Forest";

    //границы функции
    xMinRange = -42.50;
    xMaxRange = -37;
    yMinRange = -45;
    yMaxRange = -39.8;

    //координаты максимума
    globalMaxFunValue = 1.0;  // raw: 3.0269632693877031
    xGlobalMax        = -37.6991104999996764;
    yGlobalMax        = -41.9822944000032692;

    //координаты минимума
    globalMinFunValue = 0.0;  // raw: 0.0000000000000000
    xGlobalMin        = -39.0288234999996675;
    yGlobalMin        = -39.9586780000031325;
  }

  double Core (double x, double y)
  {
    double a = MathSin (MathSqrt (MathAbs (x - 1.13) + MathAbs (y - 2.0)));
    double b = MathCos (MathSqrt (MathAbs (MathSin (x))) + MathSqrt (MathAbs (MathSin (y - 2.0))));
    double f = a + b
               + 1.01 * exp (-(pow (x + 42, 2) + pow (y + 43.5, 2)) / 0.9)
               + 1.0  * exp (-(pow (x + 40.2, 2) + pow (y + 46, 2)) / 0.3);

    double res = MathPow (f, 4)
                 - 0.3  * exp (-(pow (x + 42.3, 2) + pow (y + 46.0, 2)) / 0.02);

    //return res;
    return Scale (res, 0.0000000000000000, 3.0269632693877031, 0.0, 1.0);
  }
};
//——————————————————————————————————————————————————————————————————————————————

Megacity

Оригинальная функция строится на той же базовой паре a + b, что и Forest, но после возведения в четвёртую степень применяется floor() — это создаёт дискретный ступенчатый ландшафт. Глобальный максимум определяется точками, где sin(x) = 0 и sin(y−2) = 0 одновременно, то есть x = nπ и y = 2 + mπ. Ближайшая к центру такая точка давала нормированные координаты x_norm ≈ 0.858, y_norm ≈ 0.610, расстояние от диагонали: 0.248 — недостаточно далеко.

В модифицированной версии центр функции a сдвинут: вместо (x − 1.13) используется (x + 10.13), вместо (y − 2.0) — (y − 5.0). Это смещает зоны, где a достигает максимума, в другую часть пространства поиска. Нормированные координаты нового максимума: x_norm ≈ 0.072, y_norm ≈ 0.610, расстояние от диагонали: 0.538. Максимум переместился в левую часть пространства — диагональные траектории, ориентированные по оси y ≈ x, туда практически не попадают. Значение raw_max изменилось с 12 до 14 — потому что в новой точке базовая функция имеет другое значение, и floor((a+b)⁴) даёт другой результат. Именно поэтому RAW-сканирование при верификации было необходимо: без него Scale() был бы откалиброван на неверный диапазон.

Megacity

Тестовая функция Megacity

В таком виде функция Megacity теперь включена в библиотеку тестовых функций.

//——————————————————————————————————————————————————————————————————————————————
class C_Megacity : public C_Function
{
public: //===================================================================
  C_Megacity ()
  {
    fuName = "Megacity";

    //границы функции
    xMinRange = -10.0;
    xMaxRange = -2;
    yMinRange = -10.5;
    yMaxRange = 10;

    //координаты максимума
    globalMaxFunValue = 1.0;
    xGlobalMax        = -9.428;
    yGlobalMax        = 2.0009;

    //координаты минимума
    globalMinFunValue = 0.0; //-1
    xGlobalMin        = -10.0;
    yGlobalMin        = -7.66485;
  }

  double Core (double x, double y)
  {
    double a = MathSin (MathSqrt (MathAbs (x + 10.13) + MathAbs (y - 5.0)));
    double b = MathCos (MathSqrt (MathAbs (MathSin (x))) + MathSqrt (MathAbs (MathSin (y - 2.0))));
    double f = a + b;

    double res = floor (MathPow (f, 4)) -
                 floor (2 * exp (-(pow (x + 9.5, 2) + pow (y + 7.5, 2)) / 0.4));

    if (res < -1.0) res = -1.0;

    return Scale (res, -1.0, 14.0, 0.0, 1.0);
  }
};
//——————————————————————————————————————————————————————————————————————————————

Итоговое сравнение нормированных координат максимумов:

Функция x_norm ориг. y_norm ориг. Δ ориг. x_norm мод. y_norm мод. Δ мод.
Hilly 0.253 0.604 0.351 без изменений
Forest 0.591 0.731 0.140 0.873 0.580 0.293
Megacity 0.858 0.610 0.248 0.072 0.610 0.538

Hilly уже удовлетворяла критерию Δ > 0.3 и не менялась. Megacity превышает порог с большим запасом (Δ = 0.538). Forest формально не достигает порога 0.3, однако Δ вырос вдвое — с 0.140 до 0.293, — и, как покажут результаты тестирования, этого оказалось достаточно для обрушения результатов HHO. Это наблюдение говорит о том, что порог 0.3 — не абсолютная граница, а ориентир: чувствительность конкретного алгоритма к положению максимума относительно диагонали зависит от его механики.


Верификация модифицированных функций

Для точного определения экстремумов модифицированных функций и их визуального контроля был разработан вспомогательный скрипт VerifyExtremes.mq5. Он решает три задачи одновременно: находит точные сырые значения raw_min и raw_max функции до применения Scale(), рисует тепловую карту функции прямо на графике MetaTrader 5 (идентичную визуализации основного тестового стенда), и проверяет корректность калибровки через CalcFunc().

Необходимость скрипта обусловлена архитектурой классов в TestFunctions.mqh: метод Core() всегда возвращает уже нормализованный результат через Scale(). Чтобы найти настоящие raw_min и raw_max для калибровки, нужно вычислить res до Scale() — именно это делают функции CoreRaw_*(), тела которых точно повторяют Core() за исключением последней строки.

Процедура верификации после любой правки функции состоит из двух запусков. Первый запуск сканирует RAW-значения по сетке GridN×GridN (значение 10000 даёт 100 миллионов точек — достаточно для точной локализации любого экстремума) и выводит в журнал готовые строки кода для вставки в конструктор класса и в Core(). Например, для Megacity результат выглядит так:

--- вставить в конструктор класса ---
  globalMaxFunValue = 1.0;  // raw: 14.0000000000000000
  xGlobalMax        = -9.4280000000000008;
  yGlobalMax        = 2.0009000000000015;
  globalMinFunValue = 0.0;  // raw: -1.0000000000000000
  xGlobalMin        = -10.0000000000000000;
  yGlobalMin        = -7.6648499999999995;

--- вставить в Core() ---
  return Scale (res, -1.0000000000000000, 14.0000000000000000, 0.0, 1.0);

Эти строки копируются в TestFunctions.mqh, файл перекомпилируется. Второй запуск подтверждает калибровку: скрипт вызывает CalcFunc() с обновлёнными координатами и проверяет, что функция возвращает 1.0 в точке максимума и 0.0 в точке минимума.

ПРОВЕРКА CalcFunc (актуально после вставки новых Scale):
  CalcFunc в точке max = 1.00000000   (должно быть около 1.0)
  CalcFunc в точке min = 0.00000000   (должно быть около 0.0)

Если оба значения совпадают — калибровка завершена и можно запускать тестирование алгоритмов. Если скрипт выводит предупреждение «⚠ max далёк от 1.0» — это означает либо что TestFunctions.mqh не был перекомпилирован, либо найденная точка лежит на границе дискретной ячейки floor() и нестабильна — в этом случае достаточно сдвинуть xGlobalMax на несколько тысячных в сторону центра ячейки.

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

//——————————————————————————————————————————————————————————————————————————————
// RAW-функции — res ДО Scale().
// *** СИНХРОНИЗИРУЙТЕ тела с Core() в TestFunctions.mqh ***
//——————————————————————————————————————————————————————————————————————————————
double CoreRaw_Forest (double x, double y)
{
  double a = MathSin (MathSqrt (MathAbs (x - 1.13) + MathAbs (y - 2.0)));
  double b = MathCos (MathSqrt (MathAbs (MathSin (x))) + MathSqrt (MathAbs (MathSin (y - 2.0))));
  double f = a + b
             + 1.01 * MathExp (-(MathPow (x + 41.5, 2) + MathPow (y + 46.0, 2)) / 0.9)
             + 3.0  * MathExp (-(MathPow (x + 41.5, 2) + MathPow (y + 46.0, 2)) / 0.3);
  return MathPow (f, 4)
         - 0.3 * MathExp (-(MathPow (x + 42.3, 2) + MathPow (y + 46.0, 2)) / 0.02);
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CoreRaw_Megacity (double x, double y)
{
// *** СИНХРОНИЗИРОВАНО с Core() в TestFunctions.mqh ***
  double a   = MathSin (MathSqrt (MathAbs (x + 10.13) + MathAbs (y - 5.0)));
  double b   = MathCos (MathSqrt (MathAbs (MathSin (x))) + MathSqrt (MathAbs (MathSin (y - 2.0))));
  double f   = a + b;
  double res = MathFloor (MathPow (f, 4))
               - MathFloor (2.0 * MathExp (-(MathPow (x + 9.5, 2) + MathPow (y + 7.5, 2)) / 0.4));
  if (res < -1.0) res = -1.0;
  return res;
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CoreRaw_Hilly (double x, double y)
{
  return 20.0 + x*x + y*y
         - 10.0 * MathCos (2.0*M_PI*x) - 10.0 * MathCos (2.0*M_PI*y)
         - 30.0  * MathExp (-(MathPow (x-1.0,       2) + y*y) / 0.1)
         + 200.0 * MathExp (-(MathPow (x+M_PI*0.47, 2) + MathPow (y-M_PI*0.2, 2)) / 0.1)
         + 100.0 * MathExp (-(MathPow (x-0.5,       2) + MathPow (y+0.5,      2)) / 0.01)
         - 60.0  * MathExp (-(MathPow (x-1.33,      2) + MathPow (y-2.0,      2)) / 0.02)
         - 40.0  * MathExp (-(MathPow (x+1.3,       2) + MathPow (y+0.2,      2)) / 0.5)
         + 60.0  * MathExp (-(MathPow (x-1.5,       2) + MathPow (y+1.5,      2)) / 0.1);
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CoreRaw (EFunc f, double x, double y)
{
  switch (f)
  {
  case Forest:
    return CoreRaw_Forest   (x, y);
  case Megacity:
    return CoreRaw_Megacity (x, y);
  case Hilly:
    return CoreRaw_Hilly    (x, y);
  default:
    return 0.0;
  }
}
//——————————————————————————————————————————————————————————————————————————————

Блок выше нужен по одной причине: классы функций в TestFunctions.mqh не дают доступа к сырому значению res — метод Core() всегда возвращает уже нормализованный результат через Scale(). Чтобы найти настоящие raw_min и raw_max для калибровки, нужно вычислить res до Scale() — и именно это делают CoreRaw_*().

Почему только три, а не все — потому что скрипт создан специально для верификации модифицированных функций. Модифицированы в нашем исследовании только Forest и Megacity. Hilly добавлена для полноты как контрольная точка — она не менялась и её RAW-значения уже известны.

Для остальных функций из EFunc — Rastrigin, Ackley, Rosenbrock, Peaks и других — RAW-сканирование не нужно в контексте текущего исследования. Если в будущем понадобится модифицировать любую из них, достаточно добавить соответствующую CoreRaw_*() функцию и строку в switch. Шаблон один и тот же: скопировать тело Core() из класса, убрать последнюю строку return Scale(...), вернуть res напрямую.

//——————————————————————————————————————————————————————————————————————————————
// HSL-палитра — идентична основному тестовому стенду серии
// (DoubleToColor: loH=0, upH=270, Revers=true)
//——————————————————————————————————————————————————————————————————————————————
double HueToRGB (double v1, double v2, double vH)
{
  if (vH < 0) vH += 1;
  if (vH > 1) vH -= 1;
  if ((6*vH) < 1) return v1 + (v2-v1)*6*vH;
  if ((2*vH) < 1) return v2;
  if ((3*vH) < 2) return v1 + (v2-v1)*(2.0/3-vH)*6;
  return v1;
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
color HSLtoRGB (int h, double s, double l)
{
  if (s == 0.0)
  {
    int c = (int)(l*255);
    return StringToColor ((string)c+","+(string)c+","+(string)c);
  }
  double v2  = (l < 0.5) ? (l*(1.0+s)) : ((l+s)-(l*s));
  double v1  = 2.0*l - v2;
  double hue = (double)h / 360.0;
  int r = (int)(255 * HueToRGB (v1, v2, hue + 1.0/3.0));
  int g = (int)(255 * HueToRGB (v1, v2, hue));
  int b = (int)(255 * HueToRGB (v1, v2, hue - 1.0/3.0));
  return StringToColor ((string)r+","+(string)g+","+(string)b);
}

// t ∈ [0,1] → цвет; Revers=true как в стенде: max→красный (h=0), min→синий (h=270)
uint FuncColor (double t)
{
  t = MathMax (0.0, MathMin (1.0, t));
  int h = (int)((1.0 - t) * 270.0);
  return COLOR2RGB (HSLtoRGB (h, 1.0, 0.5));
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
// Тепловая карта — вызывает Core() (со Scale), рисует [0,1] через HSL
//——————————————————————————————————————————————————————————————————————————————
void DrawHeatmap (C_Function &fn, CCanvas &cv, int sz)
{
  double xMin  = fn.GetMinRangeX (), xMax = fn.GetMaxRangeX ();
  double yMin  = fn.GetMinRangeY (), yMax = fn.GetMaxRangeY ();
  double stepX = (xMax - xMin) / (sz - 1);
  double stepY = (yMax - yMin) / (sz - 1);

  for (int px = 0; px < sz; px++)
  {
    double x = xMin + px * stepX;
    for (int py = 0; py < sz; py++)
    {
      double y = yMax - py * stepY;          // py=0 → yMax (верх), как в стенде
      double v = fn.Core (x, y);             // Core() уже нормализован [0,1]
      cv.PixelSet (px, py, FuncColor (v));
    }
    if (px % 60 == 0)
      Comment (fn.GetFuncName (), " rendering  ", DoubleToString (100.0*px/sz, 0), "%");
  }

// маркер максимума — чёрное кольцо (как в стенде)
  int mxPx = (int)MathRound ((fn.GetMaxFuncX () - xMin) / stepX);
  int mxPy = (int)MathRound ((yMax - fn.GetMaxFuncY ()) / stepY);
  for (int r = 10; r <= 13; r++)
    cv.Circle (mxPx, mxPy, r, COLOR2RGB (clrBlack));

// маркер минимума — чёрное кольцо меньше
  int mnPx = (int)MathRound ((fn.GetMinFuncX () - xMin) / stepX);
  int mnPy = (int)MathRound ((yMax - fn.GetMinFuncY ()) / stepY);
  for (int r = 10; r <= 11; r++)
    cv.Circle (mnPx, mnPy, r, COLOR2RGB (clrBlack));

  Comment ("");
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
// RAW-сканирование (до Scale)
//——————————————————————————————————————————————————————————————————————————————
void ScanRaw (EFunc funcType, C_Function &fn, int N,
              double &rawMax, double &xMx, double &yMx,
              double &rawMin, double &xMn, double &yMn)
{
  double xMin  = fn.GetMinRangeX (), xMax = fn.GetMaxRangeX ();
  double yMin  = fn.GetMinRangeY (), yMax = fn.GetMaxRangeY ();
  double stepX = (xMax - xMin) / N;
  double stepY = (yMax - yMin) / N;

  rawMax = -DBL_MAX;
  rawMin = DBL_MAX;
  xMx = xMin;
  yMx = yMin;
  xMn = xMin;
  yMn = yMin;

  long total = (long)(N+1) * (N+1);
  PrintFormat ("%s — RAW сетка %d×%d = %I64d точек  stepX=%.10f  stepY=%.10f",
               fn.GetFuncName (), N, N, total, stepX, stepY);

  for (int ix = 0; ix <= N; ix++)
  {
    double x = xMin + ix * stepX;
    for (int iy = 0; iy <= N; iy++)
    {
      double y = yMin + iy * stepY;
      double v = CoreRaw (funcType, x, y);
      if (v > rawMax)
      {
        rawMax = v;
        xMx = x;
        yMx = y;
      }
      if (v < rawMin)
      {
        rawMin = v;
        xMn = x;
        yMn = y;
      }
    }
    if (ix % (N/10) == 0)
      Comment (fn.GetFuncName (), " scanning RAW  ", DoubleToString (100.0*ix/N, 0), "%");
    if (IsStopped ())
    {
      Print ("прерван");
      return;
    }
  }
  Comment ("");
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
// Уточняющий проход ±10 шагов, шаг/100
//——————————————————————————————————————————————————————————————————————————————
void RefineRaw (EFunc funcType, double xC, double yC,
                double stepX, double stepY,
                double &rawMax, double &xMx, double &yMx,
                double &rawMin, double &xMn, double &yMn)
{
  double sX = stepX*0.01, sY = stepY*0.01;
  double rX = stepX*10.0, rY = stepY*10.0;
  for (double x = xC-rX; x <= xC+rX; x += sX)
    for (double y = yC-rY; y <= yC+rY; y += sY)
    {
      double v = CoreRaw (funcType, x, y);
      if (v > rawMax)
      {
        rawMax = v;
        xMx = x;
        yMx = y;
      }
      if (v < rawMin)
      {
        rawMin = v;
        xMn = x;
        yMn = y;
      }
    }
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
// Вывод результатов + проверка CalcFunc
//——————————————————————————————————————————————————————————————————————————————
void PrintResult (C_Function &fn,
                  double rawMax, double xMx, double yMx,
                  double rawMin, double xMn, double yMn)
{
  double xMinR = fn.GetMinRangeX (), xMaxR = fn.GetMaxRangeX ();
  double yMinR = fn.GetMinRangeY (), yMaxR = fn.GetMaxRangeY ();
  double xNMx  = (xMx - xMinR) / (xMaxR - xMinR);
  double yNMx  = (yMx - yMinR) / (yMaxR - yMinR);
  double xNMn  = (xMn - xMinR) / (xMaxR - xMinR);
  double yNMn  = (yMn - yMinR) / (yMaxR - yMinR);

  Print   ("══════════════════════════════════════════════════════");
  Print   (fn.GetFuncName (), " — RAW результат");
  Print   ("──────────────────────────────────────────────────────");
  Print   ("МАКСИМУМ (raw, до Scale):");
  PrintFormat ("  raw value = %.16f", rawMax);
  PrintFormat ("  x         = %.16f   (x_norm = %.6f)", xMx, xNMx);
  PrintFormat ("  y         = %.16f   (y_norm = %.6f)", yMx, yNMx);
  Print   ("");
  Print   ("МИНИМУМ (raw, до Scale):");
  PrintFormat ("  raw value = %.16f", rawMin);
  PrintFormat ("  x         = %.16f   (x_norm = %.6f)", xMn, xNMn);
  PrintFormat ("  y         = %.16f   (y_norm = %.6f)", yMn, yNMn);
  Print   ("");
  Print   ("--- вставить в конструктор класса ---");
  PrintFormat ("  globalMaxFunValue = 1.0;  // raw: %.16f", rawMax);
  PrintFormat ("  xGlobalMax        = %.16f;", xMx);
  PrintFormat ("  yGlobalMax        = %.16f;", yMx);
  PrintFormat ("  globalMinFunValue = 0.0;  // raw: %.16f", rawMin);
  PrintFormat ("  xGlobalMin        = %.16f;", xMn);
  PrintFormat ("  yGlobalMin        = %.16f;", yMn);
  Print   ("");
  Print   ("--- вставить в Core() ---");
  PrintFormat ("  return Scale (res, %.16f, %.16f, 0.0, 1.0);", rawMin, rawMax);
  Print   ("──────────────────────────────────────────────────────");

// Проверка CalcFunc — работает только если Scale() уже обновлён в классе
  double xy [2];
  xy [0] = fn.GetMaxFuncX ();
  xy [1] = fn.GetMaxFuncY ();
  double checkMax = fn.CalcFunc (xy);
  xy [0] = fn.GetMinFuncX ();
  xy [1] = fn.GetMinFuncY ();
  double checkMin = fn.CalcFunc (xy);
  Print   ("ПРОВЕРКА CalcFunc (актуально после вставки новых Scale):");
  PrintFormat ("  CalcFunc в точке max = %.8f   (должно быть ~1.0)", checkMax);
  PrintFormat ("  CalcFunc в точке min = %.8f   (должно быть ~0.0)", checkMin);
  if (MathAbs (checkMax - 1.0) > 0.01)
    Print ("  ⚠ max далёк от 1.0 — обновите Scale() и xGlobalMax/yGlobalMax в классе");
  if (MathAbs (checkMin - 0.0) > 0.01)
    Print ("  ⚠ min далёк от 0.0 — обновите Scale() и xGlobalMin/yGlobalMin в классе");
  Print   ("══════════════════════════════════════════════════════");
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  C_Function *fn = SelectFunction (ShowFunction);
  if (fn == NULL)
  {
    Print ("Функция не выбрана");
    return;
  }

  int sz = MathMax (200, CanvasSize);
  int N  = MathMax (100, GridN);

  Print ("=== VerifyExtremes: ", fn.GetFuncName (), "  RAW сетка ", N, "×", N, " ===");

//─── 1. RAW сканирование ─────────────────────────────────────
  double rMax, xMx, yMx, rMin, xMn, yMn;
  ScanRaw (ShowFunction, *fn, N, rMax, xMx, yMx, rMin, xMn, yMn);

//─── 2. Уточнение ────────────────────────────────────────────
  double sX = (fn.GetMaxRangeX () - fn.GetMinRangeX ()) / N;
  double sY = (fn.GetMaxRangeY () - fn.GetMinRangeY ()) / N;
  RefineRaw (ShowFunction, xMx, yMx, sX, sY, rMax, xMx, yMx, rMin, xMn, yMn);
  RefineRaw (ShowFunction, xMn, yMn, sX, sY, rMax, xMx, yMx, rMin, xMn, yMn);

//─── 3. Вывод + проверка ─────────────────────────────────────
  PrintResult (*fn, rMax, xMx, yMx, rMin, xMn, yMn);

//─── 4. Тепловая карта ───────────────────────────────────────
  string objName = "__HeatMap_" + fn.GetFuncName () + "__";
  if (ObjectFind (0, objName) >= 0) ObjectDelete (0, objName);

  CCanvas cv;
  if (!cv.CreateBitmapLabel (0, 0, objName, 5, 30, sz, sz, COLOR_FORMAT_ARGB_RAW))
  {
    Print ("Canvas create failed");
    delete fn;
    return;
  }

  cv.Erase (0xFF000000);
  DrawHeatmap (*fn, cv, sz);
  cv.Update ();

  double xNMx = (xMx - fn.GetMinRangeX ()) / (fn.GetMaxRangeX () - fn.GetMinRangeX ());
  double yNMx = (yMx - fn.GetMinRangeY ()) / (fn.GetMaxRangeY () - fn.GetMinRangeY ());
  Comment (fn.GetFuncName (),
           "   raw_max=", DoubleToString (rMax, 4),
           "   raw_min=", DoubleToString (rMin, 4),
           "   max: x_norm=", DoubleToString (xNMx, 3),
           "  y_norm=", DoubleToString (yNMx, 3),
           "   [кольцо большое=max  малое=min]");

  Print ("=== Готово. Скопируй строки из журнала в TestFunctions.mqh ===");
  delete fn;
}
//——————————————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+


Результаты на модифицированном стенде

Пришло время ответить на главный вопрос: что произойдёт с результатами HHO и DOAm, когда геометрическое преимущество будет устранено?

После модификации функций Forest и Megacity мы перегнали на обновлённом стенде два алгоритма, для которых гипотеза о «читерстве» была сформулирована наиболее явно: HHO и DOAm. Протокол тестирования стандартный: 10000 вычислений на тест, три уровня размерности (10, 50 и 1000 переменных), 50 тестов (обычно проводим 10 тестов, но ввиду высокого разброса у препарируемых алгоритмов увеличили до 50).

Итак, на прежнем стенде результаты HHO:

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%)

Результаты HHO на новом стенде:

HHO|Harris Hawks Optimization|50.0|1.5|
=============================
5 Hilly's; Func runs: 10000; result: 0.5818206459092246
25 Hilly's; Func runs: 10000; result: 0.40503838036923595
500 Hilly's; Func runs: 10000; result: 0.3444513250919525
=============================
5 Forest's; Func runs: 10000; result: 0.3653235444256101
25 Forest's; Func runs: 10000; result: 0.24723176157400148
500 Forest's; Func runs: 10000; result: 0.2571406822924982
=============================
5 Megacity's; Func runs: 10000; result: 0.37786666666666685
25 Megacity's; Func runs: 10000; result: 0.2772266666666669
500 Megacity's; Func runs: 10000; result: 0.2666586666666662
=============================
All score: 3.12276 (34.70%)

Это исчерпывающее подтверждение гипотезы. Hilly не менялась — и результат HHO на ней практически не изменился (отклонения в пределах статистической погрешности), что доказывает воспроизводимость стенда. Forest была сдвинута с диагонали — и результат HHO рухнул с 0.93-0.96 до 0.24–0.36. Megacity дала аналогичное падение: с 0.63–0.70 до 0.27–0.38. Итоговый балл 34.70% — это уже не первое место, такие значения не достойны попадания в список сильнейших алгоритмов в таблице. Именно столько стоит алгоритм без структурного везения.

Визуализация 3D работы алгоритма HHO на обновлённых функциях Forest и Megacity.

Forest

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

Megacity

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

Теперь посмотрим на прежние результаты DOAm:

DOAm|Dingo Optimization Algorithm M|50.0|0.5|0.7|
=============================
5 Hilly's; Func runs: 10000; result: 0.4796838225601811
25 Hilly's; Func runs: 10000; result: 0.4536754883347326
500 Hilly's; Func runs: 10000; result: 0.4636906699500103
=============================
5 Forest's; Func runs: 10000; result: 0.9414536230352631
25 Forest's; Func runs: 10000; result: 0.8790978288567921
500 Forest's; Func runs: 10000; result: 0.9145376343407303
=============================
5 Megacity's; Func runs: 10000; result: 0.7861538461538461
25 Megacity's; Func runs: 10000; result: 0.8606153846153848
500 Megacity's; Func runs: 10000; result: 0.8480461538461537
=============================
All score: 6.62695 (73.63%)

Результаты DOAm на новом стенде:

DOAm|Dingo Optimization Algorithm M|50.0|0.5|0.7|
=============================
5 Hilly's; Func runs: 10000; result: 0.5654328879290267
25 Hilly's; Func runs: 10000; result: 0.4734061199899962
500 Hilly's; Func runs: 10000; result: 0.451127860778784
=============================
5 Forest's; Func runs: 10000; result: 0.34359830880262116
25 Forest's; Func runs: 10000; result: 0.3813698981511174
500 Forest's; Func runs: 10000; result: 0.3757424907841298
=============================
5 Megacity's; Func runs: 10000; result: 0.45680000000000015
25 Megacity's; Func runs: 10000; result: 0.5345066666666668
500 Megacity's; Func runs: 10000; result: 0.6124213333333345
=============================
All score: 4.19441 (46.60%)

Картина аналогична HHO. Hilly не менялась — результат стабилен, что подтверждает воспроизводимость стенда. Forest рухнул с 0.88–0.94 до 0.34–0.38. Megacity тоже просел с 0.79–0.86 до 0.46–0.61. Итог 46.60% вместо 73.63%. 

Характерна одна деталь: у обоих алгоритмов на новом стенде хоть и катастрофически просели итоговые результаты, но сохраняется близость значений результатов на больших и малых размерностях (у DOAm на Megacity так и вовсе поразительная картина: 0.61 на 1000 переменных против 0.45 на 10 и 0.53 на 50 переменных). Причина этого эффекта будет разобрана далее.

Стоит обратить внимание на важную деталь: на новом стенде итоговые результаты обоих алгоритмов резко упали, но близость значений между малыми и большими размерностями сохранилась. Особенно поражает DOAm на наборе Megacity: 0.61 при 1000 переменных против 0.45 при 10 и 0.53 при 50 переменных. Как такое вообще возможно? У этого «фокуса» есть простое объяснение — и, разумеется, никакой магии у подобных «читерских» алгоритмов нет. В следующей статье мы разберём этот эффект и покажем контрольный тест для всех новых алгоритмов. Алгоритмы, не прошедшие этот тест, не будут включаться в рейтинговую таблицу.

Визуализация 3D работы алгоритма DOAm на обновлённых функциях Forest и Megacity.

Forest

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

Megacity

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


Выводы

Мы прошли путь от наблюдения «первое место выглядит слишком хорошо» до формализованного метода борьбы со структурным везением. Основные выводы и практические рекомендации:

  • Диагноз ложного лидера: резкая асимметрия результатов — выдающийся балл на одной функции и провал на другой служат надёжным маркером структурного совпадения.
  • Новый критерий бенчмарка: глобальный экстремум не должен лежать близко к главным диагоналям нормированного пространства; ориентир практический — |xnorm − ynorm| ≥ 0.3 (эффект заметен уже при Δ ≈ 0.29). Этот критерий дополняет ранее установленные требования «не на границе» и «не в центре», вместе они формируют замкнутую защиту от трёх основных классов непреднамеренного «читерства».
  • Практические инструменты: модификации функций Forest и Megacity, процедура поиска и уточнения rawmin/rawmax, скрипт VerifyExtremes.mq5 для визуальной и численной верификации, и инструкция по калибровке Scale().
  • Рекомендация для практикующего трейдера/разработчика: не полагайтесь только на итоговый рейтинг. Прежде чем переносить «победителя» на реальную задачу, проверяйте симметрию результатов по функциям и положение экстремумов в нормированных координатах; при подозрении — перезапускайте тест на модифицированных функциях. На ландшафте вашего советника, который ничего не знает о диагоналях нормированного пространства, такое совпадение может не повториться.

Дальнейшая работа — прогон всех 45 алгоритмов на обновлённом стенде и пересчёт рейтинга — позволит окончательно отсеять алгоритмы, чьи результаты были завышены геометрией тестов, и выявить действительно устойчивые оптимизаторы. Весь код и модифицированные тесты прилагаются; описанные процедуры воспроизводимы и готовы к использованию.


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


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

# Имя Тип Описание
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
11  Test_DOAm.mq5 Скрипт
Испытательный стенд для DOAm
12 VerifyExtremes.mq5 Скрипт
Поиск RAW-экстремумов + 2D-тепловая карта функции
Прикрепленные файлы |
FalseLeaders.zip (426.03 KB)
Неопределённость как модель (Часть 5): Основы регрессии Неопределённость как модель (Часть 5): Основы регрессии
Практическое введение в регрессионные модели временных рядов: регрессия на константу и парная регрессия при детерминированном, экзогенном и эндогенном регрессорах. Описаны ключевые шаги диагностики, включая анализ остатков и проверку гипотез, необходимые для обоснованных торговых решений. Приложены MQL5‑скрипты для MetaTrader 5, реализующие тесты и графики на реальных данных.
Нейросети в трейдинге: Поиск устойчивых закономерностей в разнородных рыночных данных (INFNet) Нейросети в трейдинге: Поиск устойчивых закономерностей в разнородных рыночных данных (INFNet)
Статья знакомит с фреймворком INFNet, предложенным для эффективного взаимодействия признаков в многозадачных системах. Проанализированы ключевые принципы в контексте финансовых рынков. Начата адаптация предложенных подходов средствами MQL5: переосмыслена структура данных и реализован механизм генерации сценарных токенов. Создана основа для дальнейшей разработки моделей с линейной сложностью и устойчивой обработкой разнородных рыночных сигналов.
Как организовать ИИ-хедж-фонд в MetaTrader 5 Как организовать ИИ-хедж-фонд в MetaTrader 5
В статье разобрана архитектура совета из 15 ИИ-агентов: десять аналитиков и четыре риск-офицера голосуют в трёх параллельных фазах, итог фиксирует Председатель. Для восьми валютных пар используются изолированные контексты с отдельными репутациями. Динамический порог голосов зависит от дневных целей PnL. Expert Advisor работает только по сигналу SL и TP, что позволяет оценить качество решений без дополнительной механики.
От начального до среднего уровня: Указатель на функцию От начального до среднего уровня: Указатель на функцию
Вы, вероятно, уже слышали о указателях, когда речь заходит о программировании. А вы знали, что мы можем использовать данные такого типа здесь, в MQL5? Это, конечно, должно быть сделано так, чтобы мы не теряли контроль и не вызывали странного поведения программы во время её выполнения. Тем не менее, поскольку это ресурс очень специфического назначения и ориентированный на определенные виды деятельности, редко можно услышать, чтобы кто-то обсуждал, что такое указатель и как его использовать в MQL5.