461 lines
27 KiB
Plaintext
461 lines
27 KiB
Plaintext
//+——————————————————————————————————————————————————————————————————+
|
|
//| 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;
|
|
}
|
|
//———————————————————————————————————————————————————————————————————— |