Алгоритм оптимизации грифов — Buzzard Optimization Algorithm (BUZOA)
Содержание
Введение
В арсенале популяционных алгоритмов оптимизации существует метод роя частиц (PSO), на котором построено большинство современных гибридов. Базовая схема PSO известна десятилетиями, её сильные и слабые стороны изучены, и попытки её улучшить, как правило, идут в одном направлении: усложнить математику, добавить адаптивные коэффициенты, ввести новые операторы. Но что если посмотреть на задачу с другой стороны — не усложнить уравнения, а изменить саму логику принятия решений агентом? Что если каждая частица в каждый момент времени будет случайно выбирать одну из нескольких качественно разных стратегий поведения, как это делают живые существа в природе? Именно такой ход придумали авторы BUZOA, и вопрос в том, действительно ли биологически мотивированное переключение режимов даёт что-то, чего нет у классических PSO-подобных схем — или это лишь красивая метафора без практической ценности.Читатель получит полностью разобранный, реализованный на MQL5 и протестированный в составе нашего стенда метаэвристический алгоритм BUZOA — с детальным разбором его трёх режимов охоты, сравнением буквальных формул статьи Аршаги, Ашурияна и Габели (2019) с их рациональной интерпретацией, бенчмарком на тестовых функциях Hilly, Forest и Megacity, и рекомендациями по настройке параметров. Параллельно читатель увидит, как биологическая аналогия — зрение, обоняние и слежение за хищниками — переводится в математический язык скорости, инерции и случайного рассеивания, и сможет оценить, насколько эта аналогия работает на практике.
Реализация алгоритма
Грифы — одни из немногих животных, выработавших коллективную стратегию поиска пищи, опирающуюся сразу на два независимых канала информации: обоняние и зрение. Индюшачьи грифы (Cathartes aura) обладают исключительно развитым обонянием и способны учуять падаль с расстояния более километра. Чёрные грифы (Coragyps atratus) практически лишены этого чувства, однако компенсируют его острым зрением: они не ищут пищу напрямую, а следят за поведением индюшачьих сородичей сверху. Как только индюшачий гриф снижается, чёрные грифы пикируют следом и нередко оттесняют первооткрывателя.
Помимо двух этих сценариев, грифы порой обнаруживают источник пищи совершенно иначе — замечая с большой высоты скопление наземных хищников (волков, собак) вокруг добычи и немедленно слетаясь к этому месту со всей округи. Именно эти три биологических сценария и легли в основу алгоритма BUZOA.
Каждый агент популяции представляет одного грифа, характеризуемого двумя векторами. Первый — вектор позиции L, задающий текущее местоположение птицы в пространстве поиска. Второй — вектор способности C (ability vector), который по аналогии со скоростью в методе роя частиц (PSO) определяет, насколько и в каком направлении агент сместится на следующем шаге. Такое разделение позволяет сохранять «накопленный импульс» движения: гриф не останавливается резко, а плавно меняет траекторию под влиянием новой информации.
Каждый агент помнит лучшую позицию, которую он когда-либо занимал (личный рекорд pBest), а вся стая знает глобально лучшую найденную позицию gBest. Вектор способности ограничен диапазоном [−C_max, +C_max], где C_max = (L_max − L_min) / 2, то есть не превышает половины ширины пространства поиска по каждому измерению. Это предотвращает неконтролируемый разгон агентов и удерживает их в разумных границах.
Инициализация. В начале работы каждый агент получает случайную позицию, равномерно распределённую по всему пространству поиска, и случайную начальную способность из диапазона [−C_max, +C_max]. Такая инициализация обеспечивает максимальное начальное разнообразие популяции.
Рассмотрим простой пример в одномерном пространстве на отрезке [0, 100]. Пусть C_max = 50. Агент A получил начальную позицию L = 30 и начальную способность C = +12, агент B — позицию L = 70 и способность C = −8. Без какого-либо обновления агент A сместился бы в сторону увеличения координаты, агент B — в сторону уменьшения.
Три режима охоты. На каждой итерации для каждого агента независимо разыгрывается случайное равновероятное число в диапазоне [0, 1], определяющее, по какому из трёх сценариев он будет действовать в текущем шаге. Все три режима активны одновременно в разных частях популяции, что и создаёт баланс между сходимостью и разнообразием.
Режим 001 — эксплуатация. В этом режиме гриф охотится самостоятельно, без ориентации на соседей. Единственный ориентир — его собственный лучший результат pBest. Обновление способности описывается уравнением:
C(t) = α₁ · C(t−1) + c1 · r · (pBest − L)
Первый член, α₁ · C(t−1), сохраняет инерцию предыдущего движения: если птица летела вправо, она продолжит лететь вправо, но несколько медленнее (α₁ < 1). Второй член тянет агента к его личному рекорду: разность (pBest − L) показывает, насколько далеко и в каком направлении находится лучшая известная позиция, а случайный множитель r ∈ [0, 1] придаёт этому притяжению стохастический характер.
Например, агент с L = 30, C = +12, pBest = 45 при α₁ = 0.75, c1 = 1.5, r = 0.6 получит новую способность C = 0.75 · 12 + 1.5 · 0.6 · (45 − 30) = 9 + 13.5 = 22.5. После ограничения C_max = 50 значение не изменится, и новая позиция составит L = 30 + 22.5 = 52.5. Агент стремительно движется к своему рекорду.
Режим 010 — исследование через запах. Этот режим моделирует поведение стаи под руководством индюшачьего грифа. Вычисление выполняется в два этапа. Сначала рассчитывается базовый импульс C₁ по той же формуле, что и в режиме 001: агент учитывает инерцию и притяжение к личному рекорду. Затем к C₁ добавляются два дополнительных слагаемых:
C(t) = C₁ + c1 · r₁ · (pBest − L) + c2 · r₂ · (gBest − L)
Первое дополнительное слагаемое — повторное когнитивное притяжение к pBest, второе — социальное притяжение к глобальному рекорду gBest. Оба члена записаны через разности позиций, что обеспечивает размерную согласованность: скорость формируется из разностей координат, а не из разнородных величин.
Продолжим пример. Пусть агент имеет L = 30, C₁ = 22.5 (из предыдущего шага), pBest = 45, gBest = 80, r₁ = 0.4, r₂ = 0.7. Тогда C = 22.5 + 1.5 · 0.4 · (45 − 30) + 1.5 · 0.7 · (80 − 30) = 22.5 + 9 + 52.5 = 84. После обрезки до C_max = 50 получаем C = 50, новая позиция L = 30 + 50 = 80. Агент резко тянется к глобальному лидеру — именно такое поведение описывает погоню за обнаруженной падалью.
Режим 010 является наиболее мощным с точки зрения исследования: одновременное притяжение к pBest и gBest может увести агента далеко от текущей позиции, что особенно ценно в начале оптимизации, когда популяция ещё не сконцентрировалась.
Режим 100 — глобальное рассеивание. Этот режим моделирует поведение чёрного грифа, заметившего скопление хищников в совершенно другом районе. Агент бросает текущее место и немедленно перелетает туда. В алгоритме это реализуется как телепортация в случайную точку всего пространства поиска, а вектор способности при этом обнуляется — птица «приземлилась» заново и пока неподвижна.
C = 0, L = случайное из [L_min, L_max]
Если в нашем примере агент с L = 30 попадает в режим 100, он может оказаться, например, в точке L = 67, начав следующую итерацию с нулевой скоростью. Это самый радикальный из трёх режимов: агент полностью теряет накопленную историю движения, но зато исследует область, которая иначе была бы недоступна. Режим 100 является основным механизмом выхода из локальных оптимумов — благодаря ему стая не застревает навсегда в удачно найденной, но не глобальной ямке.
Обновление позиции и затухание инерции. После расчёта новой способности в режимах 001 и 010 позиция обновляется по простому правилу:
L(t) = L(t−1) + C(t)
Позиция не может выйти за границы допустимого диапазона: если L + C превышает L_max или опускается ниже L_min, значение обрезается до соответствующей границы. Аналогичное ограничение применяется к самой способности.
В конце каждой итерации коэффициент инерции α₁ немного уменьшается: α₁ ← α₁ · wDamp, где wDamp ≈ 0.99. Это плавный переход от исследования к эксплуатации по мере работы алгоритма: на ранних итерациях агенты сохраняют большую часть импульса и активно перемещаются по пространству, а ближе к концу всё больше «прижимаются» к своим рекордам.
Параметры алгоритма. Алгоритм имеет пять настраиваемых параметров. Размер популяции popSize определяет количество агентов; значение по умолчанию 50 является хорошим компромиссом между качеством поиска и вычислительными затратами. Коэффициент инерции alpha1 ∈ [0.7, 0.8] задаёт начальную «память о прошлом движении»; слишком большое значение даёт излишне инерционных агентов, слишком малое — хаотичных. Когнитивный и социальный коэффициенты c1 и c2 управляют силой притяжения к личному и глобальному рекордам соответственно; симметричные значения означают равный вес обоих ориентиров. Коэффициент затухания wDamp контролирует скорость ослабления инерции.
Связь с методом роя частиц. BUZOA является концептуальным развитием PSO. В классическом PSO скорость частицы определяется тремя слагаемыми: инерция, притяжение к личному рекорду и притяжение к глобальному рекорду, — причём все три действуют на каждом шаге для каждой частицы. В BUZOA эти три компонента разнесены по трём режимам, которые активируются стохастически: режим 001 реализует чистую инерционно-когнитивную динамику, режим 010 добавляет полноценное социальное взаимодействие, а режим 100 вводит принципиально новый элемент — полный сброс позиции, аналогов которому в стандартном PSO нет. Такое разделение позволяет алгоритму более гибко управлять балансом между исследованием и эксплуатацией в зависимости от текущего состояния каждого агента. На иллюстрации ниже рассмотрим принцип действия агентов в алгоритме.
Рисунок 1. Иллюстрация работы алгоритма BUZOA
- В центре — пространство поиска с глобальным лучшим (красная сфера g* — «добыча»), три жёлтых маркера p* — личные рекорды агентов.
- Зелёные стрелки (агент в режиме 001) — притяжение только к собственному p*, узкий локальный поиск.
- Синие стрелки (режим 010) — двойное притяжение и к p*, и к g*, классический PSO-шаг. Показано на двух агентах.
- Фиолетовая пунктирная стрелка (режим 100) — телепортация в случайную точку с обнулением скорости, кружок-цель показывает место приземления.
- Три цветных бокса по периметру — описание каждого режима с упрощённой формулой обновления способности.
- Подпись «random mode each step» в верхней части — ключевая идея, что выбор режима случаен на каждой итерации.
Переходим к написанию псевдокода.
Algorithm: BUZOA — Buzzard Optimization Algorithm Input: popSize — population size coords — problem dimensionality [L_min, L_max] — search-space bounds per coordinate α₁ — inertia coefficient [0.7 … 0.8] c₁ — cognitive coefficient (smell) [1.5 … 1.7] c₂ — social coefficient (vision) [1.5 … 1.7] w_damp — inertia decay per iteration (≈ 0.99) epochs — iteration budget Output: g*, f(g*) — best solution found INITIALISATION 1. for each coordinate c = 1 … coords do 2. C_max[c] ← (L_max[c] − L_min[c]) / 2 // eq. (14) 3. for each agent i = 1 … popSize do 4. for each coordinate c = 1 … coords do 5. L[i,c] ← uniform(L_min[c], L_max[c]) // random position 6. C[i,c] ← uniform(−C_max[c], +C_max[c]) // random ability 7. f*[i] ← −∞ // personal best fitness 8. f(g*) ← −∞ // global best fitness 9. α₁_cur ← α₁ // working inertia MAIN LOOP 10. for t = 1 … epochs do // ---------- Evaluate population ---------- 11. for each agent i do 12. f[i] ← FitnessFunction( L[i] ) // ---------- Update personal & global bests ---------- 13. for each agent i do 14. if f[i] > f*[i] then 15. f*[i] ← f[i] 16. p*[i] ← L[i] 17. if f[i] > f(g*) then 18. f(g*) ← f[i] 19. g* ← L[i] // ---------- Move every agent ---------- 20. for each agent i do 21. rMode ← uniform(0, 1) // one die roll per agent, all coords share it 22. for each coordinate c do 23. if rMode < 1/3 then // ─── MODE 001 — exploitation (eq. 8) ─── 24. r ← uniform(0, 1) 25. C[i,c] ← α₁_cur · C[i,c] + c₁ · r · ( p*[i,c] − L[i,c] ) 26. else if rMode < 2/3 then // ─── MODE 010 — exploration (eq. 9, rational form) ─── 27. r, r₁, r₂ ← uniform(0, 1) 28. C[i,c] ← α₁_cur · C[i,c] + c₁ · r · ( p*[i,c] − L[i,c] ) + c₁ · r₁ · ( p*[i,c] − L[i,c] ) + c₂ · r₂ · ( g*[c] − L[i,c] ) 29. else // ─── MODE 100 — global restart ─── 30. C[i,c] ← 0 31. L[i,c] ← uniform(L_min[c], L_max[c]) 32. continue // skip clamps & shift below // ---------- Clamp ability and shift position ---------- 33. C[i,c] ← clip( C[i,c], −C_max[c], +C_max[c] ) // eq. (13) 34. L[i,c] ← clip( L[i,c] + C[i,c], L_min[c], L_max[c] ) // eq. (10) // ---------- Decay inertia ---------- 35. α₁_cur ← α₁_cur · w_damp 36. return g*, f(g*)
Теперь можем написать реализацию. Класс наследуется от базового C_AO, от которого получает стандартную инфраструктуру популяционного алгоритма: массив агентов a[], векторы глобального лучшего решения cB[]/fB, диапазоны пространства поиска, размерность задачи coords, утилитарный объект с генераторами случайных чисел и функциями отображения. Конструктор устанавливает метаданные алгоритма и значения параметров по умолчанию.
Выбор значений соответствует рекомендациям статьи: α₁ — в середине рекомендованного интервала [0.7, 0.8] (инерция способности), c₁ и c₂ — в нижней границе интервала [1.5, 1.7] для когнитивного и социального коэффициентов, wDamp = 0.99 — стандартное для PSO-подобных методов значение коэффициента затухания инерции. popSize = 50 — компромисс между разнообразием популяции и количеством итераций при фиксированном бюджете вычислений.
Далее создаётся массив params[5] с именами и начальными значениями, через который оболочка тестового стенда может изменять настройки алгоритма извне.
Обратная операция к конструктору — извлекает значения из массива params[] и записывает их в типизированные переменные класса. Вызывается после того, как внешний код (обычно тестовый скрипт) перезаписал params[i].val. Без этого вызова алгоритм будет продолжать работать со значениями по умолчанию из конструктора.
Поля класса. Публичные поля alpha1, c1, c2, wDamp соответствуют коэффициентам α₁, α₂, γ из уравнений (8)–(9) статьи (параметр c1 в реализации выполняет роль α₂ из статьи, c2 — роль γ, имена выбраны для согласия с соглашениями PSO-литературы).
Приватные поля:
- C[] — одномерный массив размера popSize × coords, хранит векторы способности всех агентов. Доступ через вспомогательный метод Idx(i, c), который вычисляет линейный индекс. Это эквивалент Cᵢ из уравнения (2) статьи.
- Cmax[] — верхняя граница модуля способности, отдельная для каждой координаты. Реализует ограничение скорости из уравнения (14).
- a1Cur — текущее значение инерции с учётом накопленного затухания. Отделено от alpha1, чтобы базовое значение не изменялось от прогона к прогону.
Вспомогательные методы Idx (линеаризация двумерного индекса) и Clamp (ограничение значения в заданном диапазоне) нужны для компактности основного кода.
//+------------------------------------------------------------------+ class C_AO_BUZOA : public C_AO { public: ~C_AO_BUZOA() {} C_AO_BUZOA() { ao_name = "BUZOA"; ao_desc = "Buzzard Optimization Algorithm"; ao_link = "https://www.mql5.com/ru/articles/21843"; popSize = 50; alpha1 = 0.75; c1 = 1.5; c2 = 1.5; wDamp = 0.1; ArrayResize(params, 5); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "alpha1"; params [1].val = alpha1; params [2].name = "c1"; params [2].val = c1; params [3].name = "c2"; params [3].val = c2; params [4].name = "wDamp"; params [4].val = wDamp; } void SetParams() { popSize = (int)params [0].val; alpha1 = params [1].val; c1 = params [2].val; c2 = params [3].val; wDamp = params [4].val; } bool Init(const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0); void Moving(); void Revision(); //------------------------------------------------------------------ double alpha1; // коэффициент инерции способности [0.7–0.8] double c1; // когнитивный коэффициент (запах) [1.5–1.7] double c2; // социальный коэффициент (зрение) [1.5–1.7] double wDamp; // затухание инерции за итерацию [≈0.99] private: //---------------------------------------------------------- double C []; // векторы способности [popSize × coords] double Cmax []; // предел скорости: C ∈ [−Cmax, +Cmax] ур. (14) double a1Cur; // текущий α₁ (убывает с каждой итерацией) int Idx(int i, int c) { return i * coords + c; } double Clamp(double v, double lo, double hi) { return v < lo ? lo : v > hi ? hi : v; } }; //+------------------------------------------------------------------+
Метод инициализации вызывается один раз перед началом оптимизации. Он получает границы и дискретизацию пространства поиска, после чего выполняет три действия.
Стандартная инициализация базового класса. Вызов StandardInit копирует диапазоны во внутренние массивы, создаёт популяцию a[popSize], обнуляет глобальный лучший фитнес. Если инициализация невозможна (например, несовпадение размеров массивов диапазонов), метод возвращает false.
Расчёт предела скорости по формуле (14). Для каждой координаты половина диапазона поиска принимается за максимально допустимую скорость движения агента вдоль соответствующей оси. Это предотвращает ситуации, когда накопленная способность выбрасывает агента за пределы пространства поиска за один шаг.
Случайная инициализация популяции. Для каждого агента и каждой координаты:
- Позиция cP[c] — равномерно из [rangeMin[c], rangeMax[c]] .
- Способность C[i,c] — равномерно из [−Cmax[c], +Cmax[c]] .
Наконец, a1Cur = alpha1 — устанавливается начальное значение инерции для нового прогона.
//+------------------------------------------------------------------+ bool C_AO_BUZOA::Init(const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if(!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) return false; ArrayResize(C, popSize * coords); ArrayResize(Cmax, coords); //--- Предел скорости по ур. (14): C_max = (L_max − L_min) / 2 for(int c = 0; c < coords; c++) Cmax [c] = (rangeMax [c] - rangeMin [c]) * 0.5; //--- Случайная инициализация позиций и скоростей for(int i = 0; i < popSize; i++) for(int c = 0; c < coords; c++) { a [i].cP [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); C [Idx(i, c)] = u.RNDfromCI(-Cmax [c], Cmax [c]); } a1Cur = alpha1; return true; } //+------------------------------------------------------------------+
Метод Moving служит мостом между внутренним представлением положения агента (непрерывное cP[], где происходят вычисления скорости и позиции) и представлением, передаваемым на вход фитнес-функции (c[], приведённое к сетке дискретизации через SeInDiSp).
Разделение на cP и c принципиально для BUZOA: арифметика уравнений (8)–(10) требует непрерывных значений — при дискретизации разности (pBest − L) теряли бы смысл на мелких шагах. Поэтому физика алгоритма идёт в непрерывном пространстве, а дискретизация применяется только при подаче координат на оценку.
//+------------------------------------------------------------------+ void C_AO_BUZOA::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 — основной шаг алгоритма. Это сердце BUZOA. Метод вызывается после оценки фитнеса всех агентов и выполняет три последовательных действия.
Обновление личных и глобального рекордов. Для каждого агента проверяется, улучшил ли он свой личный рекорд fB (лучший фитнес, найденный этим агентом за всю историю). Если да — обновляется пара fB/cB. Аналогично для глобального рекорда класса. Сравнение соответствует задаче максимизации; направление оптимизации унаследовано от базового класса C_AO. Координаты копируются из a[i].c (уже дискретизированных), что гарантирует, что лучшая точка всегда соответствует реально оценённой позиции.
Обновление способности и позиции. Принципиально важно: режим охоты выбирается один раз на агента и одинаков для всех его координат. Это отражает биологическую логику — птица принимает единое тактическое решение, а не выбирает режим покоординатно. Решение вынесено за внутренний цикл именно для этого. Внутри цикла по координатам обрабатываются три режима.
Режим 001 — эксплуатация (rMode < 0.333): это прямая реализация уравнения (8) из статьи. В кодовой нотации α₁ → a1Cur, α₂ → c1, L*ᵢ → pBest . Формула описывает ситуацию, когда грифы охотятся сами: движение сохраняет инерцию и мягко корректируется в сторону личного рекорда. Радиус поиска ограничен, отсюда название «exploitation».
Режим 010 — исследование через запах (0.333 ≤ rMode < 0.667): рациональная интерпретация уравнения (9). Буквальная запись статьи содержит слагаемые β·(Cs − L) + (1−β)·(Cv − L) + γ·(gBest − L) с β ∈ [1.5, 1.7]. При таких значениях коэффициент (1−β) становится отрицательным, что даёт физически бессмысленное отталкивание от Cv. Очевидный дефект записи статьи, поэтому реализация отождествляет Cs с pBest, Cv с gBest, а нелогичную пару β/(1−β) заменяет на две положительные весовые функции. Результирующая формула — полный PSO-шаг: инерция + притяжение к личному рекорду + притяжение к глобальному рекорду. Сохраняется биологическая семантика: гриф-разведчик ведёт стаю к добыче, каждая птица одновременно следует и собственному опыту, и лидеру.
Режим 100 — глобальное рассеивание (rMode ≥ 0.667): чёрный гриф замечает добычу или хищника издалека и летит прямо туда. Формулы для этого режима в статье нет, только словесное описание «широкий диапазон поиска». Реализация — явная случайная ре-инициализация позиции по всему пространству с обнулением накопленной способности. continue пропускает стандартный шаг обновления, так как позиция уже задана напрямую. Это самый разрушительный из трёх режимов — периодические рестарты отдельных агентов защищают алгоритм от застревания в локальных максимумах.
Ограничение скорости и обновление позиции (для режимов 001 и 010): первая строка реализует уравнение (13) — ограничение способности диапазоном [−Cmax, +Cmax]. Вторая сохраняет новое значение способности в массив. Третья применяет уравнение (10): L(t) = L(t−1) + C(t), с дополнительным клиппингом в границы пространства поиска, чтобы агент не вышел за допустимую область.
Затухание инерции. В конце итерации a1Cur уменьшается в wDamp раз. В статье это прямо не описано, но является стандартным приёмом PSO-семейства, способствующим плавному переходу от разведки к эксплуатации по мере выполнения бюджета. При wDamp = 0.99 и 200 итерациях инерция падает с 0.75 до приблизительно 0.10, что соответствует ожидаемому поведению алгоритма: широкое исследование в начале, уточнение решений в конце.
//+------------------------------------------------------------------+ void C_AO_BUZOA::Revision() { //--- 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. Обновление способности и позиции for(int i = 0; i < popSize; i++) { // Режим охоты: одно значение на агента, одинаково для всех координат double rMode = u.RNDfromCI(0.0, 1.0); for(int c = 0; c < coords; c++) { double pos = a [i].cP [c]; // текущая позиция L(t-1) double Ci = C [Idx(i, c)]; // текущая способность C(t-1) double pBest = a [i].cB [c]; // личный рекорд double gBest = cB [c]; // глобальный рекорд double CNew; if(rMode < 0.333) { //--- Режим 001: эксплуатация // Грифы охотятся самостоятельно — быстрый, узкий поиск вблизи pBest. // C(t) = α₁·C(t-1) + c1·r·(pBest − L) ур. (8) double r = u.RNDfromCI(0.0, 1.0); CNew = a1Cur * Ci + c1 * r * (pBest - pos); } else if(rMode < 0.667) { //--- Режим 010: исследование через запах // Индейский гриф ведёт стаю к добыче. // C₁ = α₁·C + c1·r·(pBest−L) ур. (8) // C₂ = C₁ + c1·r₁·(pBest−L) + c2·r₂·(gBest−L) ур. (9) // Все члены — разности позиций, размерно согласованы. double r = u.RNDfromCI(0.0, 1.0); double r1 = u.RNDfromCI(0.0, 1.0); double r2 = u.RNDfromCI(0.0, 1.0); double C1 = a1Cur * Ci + c1 * r * (pBest - pos); CNew = C1 + c1 * r1 * (pBest - pos) // когнитивная: притяжение к pBest + c2 * r2 * (gBest - pos); // социальная: притяжение к gBest } else { //--- Режим 100: глобальное рассеивание // Чёрный гриф замечает добычу или хищника издалека и летит // прямо туда — телепортация в случайную точку всего пространства. // Способность сбрасывается: агент начинает движение с нуля. C [Idx(i, c)] = 0.0; a [i].cP [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); continue; // позиция задана напрямую, стандартное обновление не нужно } // Ограничение скорости: C ∈ [−Cmax, +Cmax] ур. (13) CNew = Clamp(CNew, -Cmax [c], Cmax [c]); C [Idx(i, c)] = CNew; // Обновление позиции: L(t) = L(t-1) + C(t) ур. (10) a [i].cP [c] = Clamp(pos + CNew, rangeMin [c], rangeMax [c]); } } //--- 3. Затухание инерции: α₁ ← α₁ · wDamp a1Cur *= wDamp; } //+------------------------------------------------------------------+
Снаружи BUZOA выглядит как стандартный C_AO-алгоритм с типовым жизненным циклом: однократный Init, затем повторяющийся цикл Moving → CalcFunc → Revision. Внутренняя логика при этом чисто PSO-подобная, с дополнением — стохастическим переключателем режимов, который случайным образом подбрасывает агентам либо стандартный PSO-шаг, либо усиленный шаг с двойным когнитивным притяжением, либо полный рестарт. Такая структура обеспечивает одновременно эксплуатацию накопленного опыта и периодическое глобальное исследование пространства поиска без необходимости дополнительных управляющих параметров или сложных механизмов балансировки.
Результаты тестов
Самое информативное в результате — не итоговая цифра, а параметры, при которых она достигнута. Авторская статья рекомендует c₁, c₂ ∈ [1.5, 1.7] и подразумевает wDamp ≈ 0.99. Эмпирический оптимум сместился в c₁ = c₂ = 1.1 (ниже рекомендованного диапазона) и wDamp = 0.67 (на порядок ниже стандартного PSO-значения).
wDamp = 0.67 означает, что инерция испаряется за 5–10 итераций: уже к десятой эпохе α₁ опускается ниже 0.013. То есть алгоритм работает лучше всего тогда, когда его собственная динамика быстро отключается, а движение сводится к чистому притяжению к p* и g*. Режимная архитектура BUZOA даёт алгоритму возможность работать хорошо, но не за счёт своих режимов, а вопреки части собственной кинематики.
Результат вне рейтинга. Алгоритм работоспособен, сходится на простых задачах, не разваливается на сложных — но и не претендует быть среди лучших.
BUZOA|Buzzard Optimization Algorithm|50.0|0.75|1.1|1.1|0.67|
=============================
5 Hilly's; Func runs: 10000; result: 0.815572465271098
25 Hilly's; Func runs: 10000; result: 0.4477550662434088
500 Hilly's; Func runs: 10000; result: 0.25901610272316844
=============================
5 Forest's; Func runs: 10000; result: 0.7121652523799167
25 Forest's; Func runs: 10000; result: 0.2537132619669822
500 Forest's; Func runs: 10000; result: 0.06530761795980346
=============================
5 Megacity's; Func runs: 10000; result: 0.6066666666666667
25 Megacity's; Func runs: 10000; result: 0.23253333333333331
500 Megacity's; Func runs: 10000; result: 0.10430666666666759
=============================
All score: 3.49704 (38.86%)
На малоразмерных версиях тестов (5 Hilly, 5 Forest, 5 Megacity) BUZOA показывает достойные 60–80%, что для пяти координат и бюджета 10 000 вычислений — нормальный результат для PSO-семейства, честный алгоритм.
Coordinates: 10; Epochs: 200; Repeats: 10
=============================
Run 1/10: 0.716178040564617
Run 2/10: 0.7331725752886197
Run 3/10: 0.7153614864917345
Run 4/10: 0.7153614864917345
Run 5/10: 0.7931022628996451
Run 6/10: 0.7931022628996451
Run 7/10: 0.7902473358804536
Run 8/10: 0.7098239581426679
Run 9/10: 0.7098239581426679
Run 10/10: 0.8013033806407709
=============================
Average result: 0.7477476747 (74.77%)
=============================
Визуализация работы алгоритма BUZOA на тестовых функциях, а также на других, стандартных.

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

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

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

BUZOA на функции Ackley

BUZOA на функции Skin
В рейтинговой таблице алгоритм BUZOA представлен ознакомительно.
cc | AO | Description | Hilly | Hilly Final | Forest | Forest Final | Megacity (discrete) | Megacity Final | Final Result | % of MAX | ||||||
| 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | ||||||||
| 1 | ANS | across neighbourhood search | 1,00000 | 0,88228 | 0,40138 | 2,28366 | 1,00000 | 0,95281 | 0,28092 | 2,23373 | 0,94667 | 0,85733 | 0,22389 | 2,02789 | 6,545 | 72,72 |
| 2 | AMOm | animal migration optimization M | 0,91624 | 0,83603 | 0,46790 | 2,22017 | 0,98482 | 0,92010 | 0,36391 | 2,26883 | 0,91733 | 0,81707 | 0,25177 | 1,98617 | 6,475 | 71,94 |
| 3 | CLA | code lock algorithm (joo) | 0,95139 | 0,86199 | 0,37879 | 2,19217 | 0,99349 | 0,93500 | 0,26497 | 2,19346 | 0,93600 | 0,84267 | 0,24060 | 2,01927 | 6,405 | 71,17 |
| 4 | (P+O)ES | (P+O) evolution strategies | 0,86571 | 0,89539 | 0,39740 | 2,15850 | 0,97761 | 0,89820 | 0,26878 | 2,14459 | 0,92133 | 0,80240 | 0,23952 | 1,96325 | 6,266 | 69,62 |
| 5 | SDSm | stochastic diffusion search M | 0,95195 | 0,84944 | 0,36249 | 2,16388 | 0,98061 | 0,88457 | 0,22112 | 2,08630 | 0,92267 | 0,79013 | 0,21380 | 1,92660 | 6,177 | 68,63 |
| 6 | AAm | archery algorithm M | 0,84685 | 0,73320 | 0,42590 | 2,00595 | 0,96709 | 0,77837 | 0,27789 | 2,02335 | 0,86133 | 0,77707 | 0,28712 | 1,92552 | 5,955 | 66,17 |
| 7 | SIA | simulated isotropic annealing (joo) | 0,93543 | 0,86504 | 0,38483 | 2,18530 | 0,94069 | 0,80609 | 0,23835 | 1,98513 | 0,86400 | 0,66160 | 0,19536 | 1,72096 | 5,891 | 65,46 |
| 8 | TETA | time evolution travel algorithm (joo) | 0,91452 | 0,86369 | 0,25579 | 2,03400 | 0,99654 | 0,91291 | 0,14394 | 2,05339 | 0,85467 | 0,82213 | 0,10443 | 1,78123 | 5,869 | 65,21 |
| 9 | ESG | evolution of social groups (joo) | 0,98111 | 0,79857 | 0,31167 | 2,09135 | 0,98954 | 0,82270 | 0,15032 | 1,96256 | 0,92133 | 0,73440 | 0,15315 | 1,80888 | 5,863 | 65,14 |
| 10 | CTA | comet tail algorithm (joo) | 0,92435 | 0,86786 | 0,27838 | 2,07059 | 0,99039 | 0,84571 | 0,19448 | 2,03058 | 0,95467 | 0,69680 | 0,11008 | 1,76155 | 5,863 | 65,14 |
| 11 | ECBO | enhanced colliding bodies optimization | 0,94024 | 0,72363 | 0,32356 | 1,98743 | 0,99477 | 0,80291 | 0,13056 | 1,92824 | 0,87600 | 0,70160 | 0,17433 | 1,75193 | 5,668 | 62,98 |
| 12 | DA | dialectical algorithm | 0,93117 | 0,75400 | 0,26205 | 1,94722 | 0,98925 | 0,81375 | 0,08662 | 1,88962 | 0,92667 | 0,68107 | 0,11315 | 1,72089 | 5,558 | 61,76 |
| 13 | BBO | biogeography based optimization | 0,95876 | 0,70609 | 0,35752 | 2,02237 | 0,92981 | 0,70660 | 0,16970 | 1,80611 | 0,87467 | 0,63013 | 0,20813 | 1,71293 | 5,541 | 61,57 |
| 14 | BHAm | black hole algorithm M | 0,79558 | 0,76207 | 0,34682 | 1,90447 | 0,99836 | 0,75798 | 0,13826 | 1,89460 | 0,85067 | 0,64427 | 0,17020 | 1,66514 | 5,464 | 60,71 |
| 15 | HS | harmony search | 0,91420 | 0,69049 | 0,29924 | 1,90393 | 0,97627 | 0,73373 | 0,14193 | 1,85193 | 0,91733 | 0,62720 | 0,15364 | 1,69817 | 5,454 | 60,60 |
| 16 | RFO | royal flush optimization (joo) | 0,80989 | 0,74481 | 0,34546 | 1,90016 | 0,95251 | 0,77926 | 0,15185 | 1,88362 | 0,80400 | 0,66427 | 0,19071 | 1,65898 | 5,443 | 60,48 |
| 17 | BOAm | billiards optimization algorithm M | 0,76177 | 0,72421 | 0,25275 | 1,73873 | 0,90890 | 0,81960 | 0,28853 | 2,01703 | 0,83733 | 0,74613 | 0,09763 | 1,68109 | 5,437 | 60,41 |
| 18 | ASO | anarchy society optimization | 0,73070 | 0,73713 | 0,31195 | 1,77978 | 0,99732 | 0,87700 | 0,17619 | 2,05051 | 0,72000 | 0,68773 | 0,18988 | 1,59761 | 5,428 | 60,31 |
| 19 | EOm | extremal optimization_M | 0,76527 | 0,75205 | 0,31908 | 1,83640 | 0,99999 | 0,76426 | 0,12437 | 1,88862 | 0,84133 | 0,64133 | 0,15247 | 1,63513 | 5,360 | 59,56 |
| 20 | ACS | artificial cooperative search | 0,75545 | 0,77162 | 0,31653 | 1,84360 | 1,00000 | 0,80488 | 0,10705 | 1,91193 | 0,76933 | 0,60800 | 0,14157 | 1,51890 | 5,274 | 58,60 |
| 21 | SSG | saplings sowing and growing | 0,75436 | 0,63206 | 0,35935 | 1,74577 | 0,91907 | 0,69694 | 0,19755 | 1,81356 | 0,81867 | 0,60533 | 0,21347 | 1,63747 | 5,197 | 57,74 |
| 22 | AOSm | atomic orbital search M | 0,76184 | 0,68435 | 0,31344 | 1,75963 | 0,90015 | 0,80044 | 0,11501 | 1,81560 | 0,82800 | 0,63280 | 0,15696 | 1,61776 | 5,193 | 57,70 |
| 23 | TSEA | turtle shell evolution algorithm (joo) | 0,95809 | 0,64852 | 0,29571 | 1,90232 | 0,99522 | 0,58104 | 0,10542 | 1,68168 | 0,92133 | 0,52160 | 0,14567 | 1,58860 | 5,173 | 57,48 |
| 24 | DE | differential evolution | 0,96398 | 0,62346 | 0,26089 | 1,84833 | 0,98482 | 0,77018 | 0,11459 | 1,86959 | 0,93067 | 0,36213 | 0,11000 | 1,40280 | 5,121 | 56,90 |
| 25 | BIO | blood inheritance optimization (joo) | 0,72580 | 0,66522 | 0,31228 | 1,70330 | 0,99995 | 0,68125 | 0,11540 | 1,79660 | 0,85467 | 0,59333 | 0,15364 | 1,60164 | 5,102 | 56,69 |
| 26 | (PO)ES | (PO) evolution strategies | 0,73972 | 0,58190 | 0,38896 | 1,71058 | 0,91199 | 0,59975 | 0,21262 | 1,72436 | 0,82400 | 0,56240 | 0,23432 | 1,62072 | 5,056 | 56,18 |
| 27 | BO | bonobo optimizer | 0,75555 | 0,64366 | 0,32657 | 1,72578 | 0,94332 | 0,70442 | 0,13999 | 1,78773 | 0,73467 | 0,61440 | 0,16728 | 1,51635 | 5,030 | 55,89 |
| 28 | SRA | successful restaurateur algorithm (joo) | 0,89010 | 0,63359 | 0,29115 | 1,81484 | 0,96634 | 0,55285 | 0,08914 | 1,60833 | 0,89333 | 0,52800 | 0,13911 | 1,56044 | 4,984 | 55,38 |
| 29 | CRO | chemical reaction optimisation | 0,91281 | 0,65681 | 0,29866 | 1,86828 | 0,90513 | 0,56020 | 0,10939 | 1,57472 | 0,82800 | 0,50133 | 0,14149 | 1,47082 | 4,914 | 54,60 |
| 30 | BCOm | bacterial chemotaxis optimization M | 0,82589 | 0,61733 | 0,31584 | 1,75906 | 0,95296 | 0,63718 | 0,11984 | 1,70998 | 0,76533 | 0,51653 | 0,15800 | 1,43986 | 4,909 | 54,54 |
| 31 | DOA | dream optimization algorithm | 0,78522 | 0,78121 | 0,36036 | 1,92679 | 0,61584 | 0,42117 | 0,12254 | 1,15955 | 0,86667 | 0,72587 | 0,21127 | 1,80381 | 4,890 | 54,33 |
| 32 | ABO | african buffalo optimization | 0,92295 | 0,62528 | 0,29885 | 1,84708 | 0,92992 | 0,57468 | 0,09372 | 1,59832 | 0,73333 | 0,51333 | 0,14324 | 1,38990 | 4,835 | 53,72 |
| 33 | BSA | bird swarm algorithm | 0,94432 | 0,67941 | 0,26401 | 1,88774 | 0,91649 | 0,65619 | 0,12054 | 1,69322 | 0,80933 | 0,33547 | 0,10652 | 1,25132 | 4,832 | 53,69 |
| 34 | TSm | tabu search M | 0,87806 | 0,61040 | 0,28993 | 1,77839 | 0,98116 | 0,52165 | 0,08544 | 1,58825 | 0,82667 | 0,49547 | 0,13552 | 1,45766 | 4,824 | 53,60 |
| 35 | BSA | backtracking search algorithm | 0,87128 | 0,53190 | 0,28675 | 1,68993 | 0,92408 | 0,51602 | 0,09153 | 1,53163 | 0,96000 | 0,47253 | 0,13760 | 1,57013 | 4,792 | 53,24 |
| 36 | WOAm | whale optimization algorithm M | 0,93893 | 0,59477 | 0,26695 | 1,80065 | 0,98036 | 0,53873 | 0,07112 | 1,59021 | 0,78667 | 0,47600 | 0,11892 | 1,38159 | 4,772 | 53,02 |
| 37 | CSO | competitive swarm optimizer | 0,85151 | 0,60786 | 0,29896 | 1,75833 | 0,84085 | 0,58491 | 0,11974 | 1,54550 | 0,80000 | 0,48560 | 0,14184 | 1,42744 | 4,731 | 52,57 |
| 38 | FBA | fractal-based algorithm | 0,69419 | 0,64267 | 0,28955 | 1,62641 | 0,99812 | 0,54905 | 0,08705 | 1,63422 | 0,76133 | 0,51253 | 0,13689 | 1,41075 | 4,671 | 51,90 |
| 39 | ECOi | eco-inspired evolutionary algorithm | 0,78817 | 0,54402 | 0,29360 | 1,62579 | 0,88996 | 0,46592 | 0,09747 | 1,45335 | 0,78533 | 0,45173 | 0,14295 | 1,38001 | 4,459 | 49,54 |
| 40 | BSO | brain storm optimization | 0,92207 | 0,57625 | 0,29732 | 1,79564 | 0,80764 | 0,42508 | 0,09448 | 1,32720 | 0,77200 | 0,36533 | 0,13065 | 1,26798 | 4,391 | 48,79 |
| 41 | CAm | camel algorithm M | 0,71534 | 0,56917 | 0,35985 | 1,64436 | 0,84094 | 0,47174 | 0,10850 | 1,42118 | 0,70400 | 0,41947 | 0,19563 | 1,31910 | 4,385 | 48,72 |
| 42 | ACOm | ant colony optimization_M | 0,71885 | 0,48410 | 0,30990 | 1,51285 | 0,75792 | 0,48639 | 0,11871 | 1,36302 | 0,83600 | 0,48667 | 0,16148 | 1,48415 | 4,360 | 48,44 |
| 43 | BSO | brain storm optimization | 0,91331 | 0,55813 | 0,29705 | 1,76849 | 0,85329 | 0,44038 | 0,09447 | 1,38814 | 0,72267 | 0,32880 | 0,13325 | 1,18472 | 4,341 | 48,23 |
| 44 | AEFA | artificial electric field algorithm | 0,88798 | 0,65769 | 0,25276 | 1,79843 | 0,95550 | 0,63602 | 0,03946 | 1,63098 | 0,60800 | 0,16000 | 0,09845 | 0,86645 | 4,296 | 47,73 |
| 45 | SOA | simple optimization algorithm | 0,91664 | 0,47040 | 0,27095 | 1,65799 | 0,88264 | 0,31546 | 0,06044 | 1,25854 | 0,82933 | 0,31733 | 0,11579 | 1,26245 | 4,179 | 46,43 |
| BUZOA | buzzards_optimization_algorithm | 0,81557 | 0,44775 | 0,25901 | 1,52233 | 0,71216 | 0,25371 | 0,06530 | 1,03117 | 0,60666 | 0,23253 | 0,10430 | 0,94349 | 3,497 | 38,86 | |
| RW | random walk | 0,49970 | 0,32333 | 0,25791 | 1,08094 | 0,30754 | 0,11470 | 0,04400 | 0,46624 | 0,36133 | 0,17013 | 0,10244 | 0,63390 | 2,181 | 24,23 | |
Выводы
Что подтвердилось из биологической метафоры. Несмотря на эти структурные особенности, центральная идея — стохастическое переключение между качественно разными стратегиями — подтвердила свою полезность. Это видно по поведению алгоритма на задачах с большим количеством локальных ловушек: режим 100 (телепортация в случайную точку) действительно спасает агентов из бассейнов притяжения локальных максимумов.
Метафора «зрение + обоняние + слежение за хищниками» переводится в математику осмысленно: режим 001 = локальный поиск по личному опыту, режим 010 = коллективное движение к лидеру, режим 100 = разрыв пространственной близости. Биологически это разные типы поведения — и алгоритмически они тоже работают по-разному. В этом смысле введение «обещало» не пустую метафору, и обещание выполнено.
Что не подтвердилось. Деградация результатов при росте размерности (с 5 до 500 функций) у BUZOA выражена сильнее, чем у топовых алгоритмов рейтинга. На 500-мерной Forest результат падает до 6.5% — алгоритм фактически теряет способность находить хорошие решения. Это означает, что преимущество от трёхрежимной архитектуры исчерпывается на малых размерностях и не масштабируется. Статья 2019 года тестировала BUZOA на CEC2014 с фиксированной размерностью 30, и в этом режиме алгоритм действительно демонстрирует конкурентоспособные результаты — но за пределами этой ниши его сильные стороны размываются.
Возврат к исходному вопросу. Был ли смысл в новой архитектуре, или это «PSO в перьях»? Ответ: смысл есть, но архитектура реализована не оптимально. Идея случайного переключения режимов с разной балансом exploration/exploitation продуктивна — особенно в виде режима 100, который аналогичен модной сегодня технике «soft restart» в эволюционных стратегиях. Однако конкретное воплощение 2019 года содержит структурные недочёты (двойное когнитивное давление, чрезмерный C_max, противоречивая буквальная запись формулы режима 010, требующая «рациональной интерпретации»), которые мешают алгоритму раскрыть свой потенциал.
Это типичная судьба алгоритмов «второго эшелона» из метаэвристического бума 2010-х: интересная биологическая идея, работоспособная реализация, но недо-инженерия в деталях. Для практического применения BUZOA в задачах оптимизации торговых стратегий он окажется в средней позиции — лучше, чем простой случайный поиск, заметно слабее, чем хорошо настроенные алгоритмы из верхней половины нашего рейтинга. Для теоретического интереса — его трёхрежимная архитектура заслуживает развития: возможные модификации (адаптивная вероятность режима 100, замена телепортации на лёвиевский полёт, симметризация формулы режима 010) могут улучшить результаты, и это направление достойно отдельного исследования.
Итог. BUZOA честно отрабатывает свою биологическую метафору, демонстрирует работоспособность всех заявленных механизмов и подтверждает идею пользы стохастической смены стратегий. Одновременно он показывает, что новая архитектура без аккуратной инженерной доводки даёт скромный результат. Читатель уходит из статьи не только с готовым к использованию кодом, но и с пониманием того, как читать формулы из академических работ критически: видеть в них не священный текст, а инженерное предложение, требующее проверки и иногда — рациональной интерпретации.
Рисунок 2. Цветовая градация алгоритмов по соответствующим тестам

Рисунок 3. Гистограмма результатов тестирования алгоритмов (по шкале от 0 до 100, чем больше, тем лучше, где 100 — максимально возможный теоретический результат), в архиве скрипт для расчета рейтинговой таблицы
Плюсы и минусы алгоритма
Плюсы:
- Решает простые задачи.
Минусы:
- Слабые показатели на высоких размерностях задач.
К статье прикреплён архив с актуальными версиями кодов алгоритмов. Автор статьи не несёт ответственности за абсолютную точность в описании канонических алгоритмов, во многие из них были внесены изменения для улучшения поисковых возможностей. Выводы и суждения, представленные в статьях, основываются на результатах проведённых экспериментов.
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | #C_AO.mqh | Включаемый файл | Родительский класс популяционных алгоритмов оптимизации |
| 2 | #C_AO_enum.mqh | Включаемый файл | Перечисление популяционных алгоритмов оптимизации |
| 3 | TestFunctions.mqh | Включаемый файл | Библиотека тестовых функций |
| 4 | TestStandFunctions.mqh | Включаемый файл | Библиотека функций тестового стенда |
| 5 | TestStand3D.mqh | Включаемый файл | 3D-панель визуализации для тестового стенда |
| 6 | Utilities.mqh | Включаемый файл | Библиотека вспомогательных функций |
| 7 | CalculationTestResults.mqh | Включаемый файл | Скрипт для расчета результатов в сравнительную таблицу |
| 8 | Test_AO_All.mq5 | Скрипт | Единый испытательный стенд для всех популяционных алгоритмов оптимизации |
| 9 | Test_AO_AntiCheat | Скрипт | Тест на читерство алгоритмов оптимизации |
| 10 | Simple use of population optimization algorithms.mq5 | Скрипт | Простой пример использования популяционных алгоритмов оптимизации без визуализации |
| 11 | Test_AO_BUZOA.mq5 | Скрипт | Испытательный стенд для BUZOA |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Dynamic Swing Architecture: Распознавание структуры рынка — от свингов до автоматического исполнения сделок
Разработка инструментария для анализа Price Action (Часть 35): Обучение и развертывание прогнозных моделей
Архитектура машинного обучения в MetaTrader 5 (Часть 6): Проектирование системы кэширования промышленного уровня
Нейросети в трейдинге: Поиск устойчивых закономерностей в разнородных рыночных данных (Окончание)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования

