//+——————————————————————————————————————————————————————————————————+ //| C_AO_BRO | //| Copyright 2007-2025, Andrey Dik | //| https://www.mql5.com/ru/users/joo | //+——————————————————————————————————————————————————————————————————+ #include "#C_AO.mqh" //———————————————————————————————————————————————————————————————————— // Упрощённая структура для CEO (адаптация к непрерывной оптимизации) struct S_CEO_Crystal { bool isElite; // элитный кристалл int stagnationCnt; // счётчик стагнации double radius; // радиус влияния void Init () { isElite = false; stagnationCnt = 0; radius = 0.0; } }; //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— class C_AO_CEO : public C_AO { public: ~C_AO_CEO () { } C_AO_CEO () { ao_name = "CEO"; ao_desc = "Crystal Energy Optimizer"; ao_link = "https://www.mql5.com/ru/articles/20052"; popSize = 50; eliteSize = 10; // элитных (аналог замёрзших) explorationRate = 0.5; // баланс исследование/эксплуатация windProbability = 0.1; // вероятность "ветра" на итерацию ArrayResize (params, 4); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "eliteSize"; params [1].val = eliteSize; params [2].name = "explorationRate"; params [2].val = explorationRate; params [3].name = "windProbability"; params [3].val = windProbability; } void SetParams () { popSize = (int)params [0].val; eliteSize = (int)params [1].val; explorationRate = params [2].val; windProbability = params [3].val; if (eliteSize < 2) eliteSize = 2; if (eliteSize > popSize / 2) eliteSize = popSize / 2; if (explorationRate < 0.1) explorationRate = 0.1; if (explorationRate > 0.9) explorationRate = 0.9; if (windProbability < 0.0) windProbability = 0.0; if (windProbability > 0.5) windProbability = 0.5; } bool Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP); void Moving (); void Revision (); //------------------------------------------------------------------ int eliteSize; double explorationRate; double windProbability; private: //--------------------------------------------------------- S_CEO_Crystal crystalData []; int iterationCount; int noImprovementCount; double centerMass []; // центр масс элитных void InitializePopulation (); void UpdateEliteStatus (); void CrystalGrowth (); // аналог осаждения void WindEffect (); // аналог wind-blow void MoveTowardsBest (int idx, double strength); void MoveTowardsEliteCluster (int idx); void ExploratoryMove (int idx); int FindNearestElite (int idx); void CalculateEliteRadii (); double GetDynamicExplorationRate (); }; //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— bool C_AO_CEO::Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP) { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //------------------------------------------------------------------ ArrayResize (crystalData, popSize); ArrayResize (centerMass, coords); for (int i = 0; i < popSize; i++) crystalData [i].Init (); iterationCount = 0; noImprovementCount = 0; return true; } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::Moving () { if (!revision) { InitializePopulation (); revision = true; return; } //------------------------------------------------------------------ iterationCount++; // Вычисляем центр масс элитных ArrayInitialize (centerMass, 0.0); int eliteCount = 0; for (int i = 0; i < popSize; i++) { if (crystalData [i].isElite) { for (int c = 0; c < coords; c++) centerMass [c] += a [i].c [c]; eliteCount++; } } if (eliteCount > 0) { for (int c = 0; c < coords; c++) centerMass [c] /= (double)eliteCount; } else { ArrayCopy (centerMass, cB, 0, 0, coords); } // Рассчитываем радиусы влияния элитных CalculateEliteRadii (); // Основная фаза: рост кристаллов CrystalGrowth (); // Периодический эффект ветра if (u.RNDfromCI (0.0, 1.0) < windProbability) { WindEffect (); } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::InitializePopulation () { for (int i = 0; i < popSize; i++) { for (int j = 0; j < coords; j++) { a [i].c [j] = u.RNDfromCI (rangeMin [j], rangeMax [j]); a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]); } } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::UpdateEliteStatus () { S_AO_Agent temp; S_CEO_Crystal tempData; // Сортируем по фитнесу for (int i = 0; i < popSize - 1; i++) { for (int j = 0; j < popSize - i - 1; j++) { if (a [j].f < a [j + 1].f) { temp = a [j]; a [j] = a [j + 1]; a [j + 1] = temp; tempData = crystalData [j]; crystalData [j] = crystalData [j + 1]; crystalData [j + 1] = tempData; } } } // Обновляем элитный статус for (int i = 0; i < popSize; i++) { crystalData [i].isElite = (i < eliteSize); } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::CrystalGrowth () { double explRate = GetDynamicExplorationRate (); for (int i = 0; i < popSize; i++) { if (crystalData [i].isElite) { // Элитные делают локальный поиск for (int c = 0; c < coords; c++) { double range = rangeMax [c] - rangeMin [c]; double stepSize = range * 0.05 * explRate; double step = u.RNDfromCI (-stepSize, stepSize); a [i].c [c] = a [i].c [c] + step; a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } else { // Не-элитные: комбинация стратегий double strategy = u.RNDfromCI (0.0, 1.0); if (strategy < 0.4) { // Движение к лучшему (40%) MoveTowardsBest (i, 0.3 + 0.4 * (1.0 - explRate)); } else if (strategy < 0.7) { // Движение к ближайшему элитному (30%) MoveTowardsEliteCluster (i); } else { // Исследование (30%) ExploratoryMove (i); } } } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::MoveTowardsBest (int idx, double strength) { for (int c = 0; c < coords; c++) { double direction = cB [c] - a [idx].c [c]; double move = strength * direction; a [idx].c [c] = a [idx].c [c] + move; a [idx].c [c] = u.SeInDiSp (a [idx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::MoveTowardsEliteCluster (int idx) { int nearestElite = FindNearestElite (idx); if (nearestElite < 0) return; for (int c = 0; c < coords; c++) { // Движение к ближайшему элитному double toElite = a [nearestElite].c [c] - a [idx].c [c]; // Движение к центру масс элитных double toCenter = centerMass [c] - a [idx].c [c]; double r1 = u.RNDfromCI (0.0, 1.0); double r2 = u.RNDfromCI (0.0, 1.0); a [idx].c [c] = a [idx].c [c] + r1 * 0.3 * toElite + r2 * 0.2 * toCenter; a [idx].c [c] = u.SeInDiSp (a [idx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::ExploratoryMove (int idx) { for (int c = 0; c < coords; c++) { double range = rangeMax [c] - rangeMin [c]; // Простой но эффективный метод для исследования double r = u.RNDfromCI (0.0, 1.0); double move; if (r < 0.7) { // 70% - небольшие шаги (локальное исследование) move = u.RNDfromCI (-1.0, 1.0) * range * explorationRate * 0.1; } else { // 30% - большие прыжки (глобальное исследование) move = u.RNDfromCI (-1.0, 1.0) * range * explorationRate * 0.5; } a [idx].c [c] = a [idx].c [c] + move; a [idx].c [c] = u.SeInDiSp (a [idx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::WindEffect () { // "Ветер" разрушает худшие не-элитные и переносит их int worstNonElite = -1; double worstFitness = DBL_MAX; for (int i = 0; i < popSize; i++) { if (!crystalData [i].isElite && a [i].f < worstFitness) { worstFitness = a [i].f; worstNonElite = i; } } if (worstNonElite < 0) return; // Перемещаем в новое место double strategy = u.RNDfromCI (0.0, 1.0); if (strategy < 0.5) { // Около лучшего for (int c = 0; c < coords; c++) { double range = rangeMax [c] - rangeMin [c]; double noise = u.RNDfromCI (-1.0, 1.0) * range * 0.3; a [worstNonElite].c [c] = cB [c] + noise; if (a [worstNonElite].c [c] < rangeMin [c]) a [worstNonElite].c [c] = rangeMin [c]; if (a [worstNonElite].c [c] > rangeMax [c]) a [worstNonElite].c [c] = rangeMax [c]; a [worstNonElite].c [c] = u.SeInDiSp (a [worstNonElite].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } else { // Полностью случайно for (int c = 0; c < coords; c++) { double xi = u.RNDfromCI (0.0, 1.0); a [worstNonElite].c [c] = rangeMin [c] + xi * (rangeMax [c] - rangeMin [c]); a [worstNonElite].c [c] = u.SeInDiSp (a [worstNonElite].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— int C_AO_CEO::FindNearestElite (int idx) { int nearest = -1; double minDist = DBL_MAX; for (int i = 0; i < popSize; i++) { if (i == idx) continue; if (!crystalData [i].isElite) continue; double dist = 0.0; for (int c = 0; c < coords; c++) { double diff = a [idx].c [c] - a [i].c [c]; dist += diff * diff; } dist = MathSqrt (dist); if (dist < minDist) { minDist = dist; nearest = i; } } return nearest; } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::CalculateEliteRadii () { // Вычисляем радиусы влияния элитных на основе их взаимных расстояний for (int i = 0; i < popSize; i++) { if (!crystalData [i].isElite) continue; double minDistToOtherElite = DBL_MAX; for (int j = 0; j < popSize; j++) { if (i == j) continue; if (!crystalData [j].isElite) continue; double dist = 0.0; for (int c = 0; c < coords; c++) { double diff = a [i].c [c] - a [j].c [c]; dist += diff * diff; } dist = MathSqrt (dist); if (dist < minDistToOtherElite) { minDistToOtherElite = dist; } } crystalData [i].radius = minDistToOtherElite / 2.0; } } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— double C_AO_CEO::GetDynamicExplorationRate () { // Адаптивная интенсивность исследования // Начинаем с высокой, уменьшаем со временем double progress = (double)iterationCount / 100.0; if (progress > 1.0) progress = 1.0; return explorationRate * (1.0 - 0.7 * progress); } //———————————————————————————————————————————————————————————————————— //———————————————————————————————————————————————————————————————————— void C_AO_CEO::Revision () { double prevBest = fB; // Обновляем статус элитных кристаллов UpdateEliteStatus (); fB = a [0].f; ArrayCopy (cB, a [0].c, 0, 0, coords); // Отслеживаем стагнацию if (MathAbs (fB - prevBest) < 1e-10) noImprovementCount++; else noImprovementCount = 0; } //————————————————————————————————————————————————————————————————————