珊瑚礁优化算法(CRO)·综合运用
🪸

珊瑚礁优化算法(CRO)·综合运用

(3/3)·从礁石网格到收敛曲线,一篇看清 CROm 在 MT5 实盘优化里的完整落地路径

含代码示例偏理论 第 3/3 篇
很多人把仿生优化当成黑箱调参玩具,拿到多峰函数就乱撞。CROm 引入的逆幂律淘汰,本质是把探索余量重新分配到高潜力邻域,理解这点才不会在收敛早期误判失效。

◍ 珊瑚群淘汰与坐标索引的实现细节

这段逻辑来自一个基于珊瑚礁优化的EA框架,核心是在每一代迭代中按适应度淘汰最差的个体。代码先用对称交换把 occupiedIndices 数组首尾对调,随后根据 Fd 参数计算要清除的珊瑚数量:removeCount = MathRound(Fd * occupiedCount),若结果 ≤0 则强制保留至少 1 个,若超过已占用数则截断为 occupiedCount,避免越界清空整个礁体。 淘汰时逐位把 occupied[pos] 置 false、reefIndices[pos] 置 -1,所有写操作前都做了 pos 在 [0, totalReefSize) 的边界校验。对外汇或贵金属实盘而言,这类群体算法对参数 Fd 极敏感,Fd 设错可能让种群过早收敛或抖动不止,属高风险调参。 GetReefCoordIndex 把二维 (row,col) 压成一维索引:row * reefCols + col,越界直接返回 -1。SortAgentsByFitness 则用冒泡按适应度降序排 indices,内层比较 reefIndices 映射后的个体适应度 a[].f,交换前双重确认 idx 在 [0, popSize) 内。 直接在 MT5 里建个继承 C_AO_CRO 的测试类,把 reefRows=10、reefCols=10、Fd=0.2 灌进去,看 removeCount 是否如预期淘汰约 20% 占用位,能快速验证这套边界保护有没有漏。

MQL5 / C++
if(i < occupiedCount && (occupiedCount - class="num">1 - i) < occupiedCount) class=class="str">"cmt">// Check for out-of-bounds
{
   class="type">int temp = occupiedIndices[i];
   occupiedIndices[i] = occupiedIndices[occupiedCount - class="num">1 - i];
   occupiedIndices[occupiedCount - class="num">1 - i] = temp;
}
}
class=class="str">"cmt">// Remove the worst Fd% corals
class="type">int removeCount = (class="type">int)MathRound(Fd * occupiedCount);
if (removeCount <= class="num">0) removeCount = class="num">1; class=class="str">"cmt">// At least one coral
if (removeCount > occupiedCount) removeCount = occupiedCount; class=class="str">"cmt">// Overflow protection
for (class="type">int i = class="num">0; i < removeCount; i++)
{
   if(i < occupiedCount) class=class="str">"cmt">// Check for out-of-bounds
   {
      class="type">int pos = occupiedIndices[i];
      if(pos >= class="num">0 && pos < totalReefSize) class=class="str">"cmt">// Additional check
      {
         occupied[pos] = class="kw">false;
         reefIndices[pos] = -class="num">1;
      }
   }
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">int C_AO_CRO::GetReefCoordIndex(class="type">int row, class="type">int col)
{
   class=class="str">"cmt">// Check for out-of-bounds
   if (row < class="num">0 || row >= reefRows || col < class="num">0 || col >= reefCols) class="kw">return -class="num">1;
   class=class="str">"cmt">// Convert a two-dimensional position to a one-dimensional index
   class="kw">return row * reefCols + col;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_CRO::SortAgentsByFitness(class="type">int &indices [], class="type">int &count)
{
   class=class="str">"cmt">// Check for an empty array
   if (count <= class="num">0) class="kw">return;
   class=class="str">"cmt">// Bubble sort by decreasing fitness
   for (class="type">int i = class="num">0; i < count - class="num">1; i++)
   {
      for (class="type">int j = class="num">0; j < count - i - class="num">1; j++)
      {
         if (j + class="num">1 < count) class=class="str">"cmt">// Check that j+class="num">1 is not out of bounds
         {
            class="type">int idx1 = reefIndices [indices [j]];
            class="type">int idx2 = reefIndices [indices [j + class="num">1]];
            if (idx1 >= class="num">0 && idx1 < popSize && idx2 >= class="num">0 && idx2 < popSize) class=class="str">"cmt">// Check indices
            {
               if (a [idx1].f < a [idx2].f)
               {
                  class="type">int temp = indices [j];
                  indices [j] = indices [j + class="num">1];
                  indices [j + class="num">1] = temp;
               }
            }
         }
      }
   }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

「珊瑚礁优化的淘汰机制重构与回测对比」

原始 CRO 在三类测试函数上跑完 10000 次迭代,总分仅 1.82096(相当于满分基准的 20.23%);其中 Megacity 离散任务在 500 规模下结果掉到 0.09366,说明原淘汰机制把差解留得太久。 问题出在旧版只做随机替换,没有把搜索压力引向精英邻域。改法很直接:每轮选定 eliteCount 个最优珊瑚和 removeCount 个最差珊瑚,用幂参数 power=0.1 的逆幂律分布,在精英解附近生成偏离量来填洞——大多数新解贴着最优,小概率跳远,兼顾开发与探索。 改完的 CROm 参数换成 50 种群 / 20 精英 / 20 淘汰 / 0.99 交叉,同样跑三组规模。5 Hilly 结果从 0.365 升到 0.785,500 Forest 从 0.168 降到 0.163 的误差值反而更小,总分拉到 3.89459(43.27%),几乎翻倍。 不过 CROm 在种群排名表里只排第 42,头部 ANS 跨邻域搜索拿 6.134(68.15%)。它在 Megacity 离散任务上仍吃力:500 规模结果 0.107,比 ANS 的 0.231 差了一截,可视化也一般。外汇与贵金属模型若想借用这类群体优化,先记住离散约束下的退化风险很高,回测必须分函数类型拆开看。

珊瑚礁优化里被吃掉的最差解

上面那张横向对比表里,CROm(珊瑚礁优化算法)综合评分 3.895、占比 43.27%,排在第 42 位,落后于 WOAm、AEFA 这类群体智能算法,但比中央力优化 CFO 的 3.668 还是要稳一点。它的核心机制之一就是在迭代中按概率 Pd 执行捕食(Depredation),把种群里最差的一部分珊瑚个体清掉,腾位置给精英附近生成的新解。 下面这段 MT5 里的 C_AO_CRO::Depredation 函数就是干这事的原生逻辑。注意它先随机抽一个 0~1 的数,小于 Pd 才触发;接着扫一遍 occupied 数组收集所有被占用的礁格索引,若全空直接 return。 //—————————————————————————————————————————————————————————————————————————————— void C_AO_CRO::Depredation () { // Apply destruction with Pd probability if (u.RNDfromCI (0, 1) < Pd) { // Find all occupied positions and their indices int occupiedIndices []; int occupiedCount = 0; for (int i = 0; i < totalReefSize; i++) { if (occupied [i]) { ArrayResize (occupiedIndices, occupiedCount + 1); occupiedIndices [occupiedCount] = i; occupiedCount++; } } // If there are no occupied positions, exit if (occupiedCount == 0) return; // Sort the indices by fitness (from best to worst) SortAgentsByFitness (occupiedIndices, occupiedCount); // Determine the number of best solutions used to generate new ones int eliteCount = (int)MathRound (0.1 * occupiedCount); // Use top 10% if (eliteCount <= 0) eliteCount = 1; // At least one elite solution if (eliteCount > occupiedCount) eliteCount = occupiedCount; // Determine the number of worst solutions to be replaced int removeCount = (int)MathRound (Fd * occupiedCount); if (removeCount <= 0) removeCount = 1; // At least one solution is replaced if (removeCount > occupiedCount - eliteCount) removeCount = occupiedCount - eliteCount; // Do not remove elite solutions // Remove the worst solutions and replace them with new ones in the vicinity of the best ones for (int i = 0; i < removeCount; i++) { // Index of the solution to be removed (from the end of the sorted array) int removeIndex = occupiedCount - 1 - i;

if (removeIndex < 0removeIndex >= occupiedCount) continue;

int posToRemove = occupiedIndices [removeIndex]; 逐行拆一下:第 4 行用 RNDfromCI 抽均匀随机数比对 Pd,决定本代是否捕食;第 7–17 行把 occupied 里为 true 的礁格压进 occupiedIndices,顺手计 occupiedCount;第 21 行占空则退出;第 24 行按适应度给占用索引排降序;第 27–31 行算精英数,默认取占用数 10% 且至少留 1 个;第 34–38 行按 Fd 算要淘汰的最差个数,并卡死不让动精英;第 41 行起从排序尾端倒着捞最差位,posToRemove 就是待清除礁格。 在 MT5 里把 Pd 和 Fd 两个参数拉出来试,Fd 设 0.2 时每代清掉约两成最差解,种群多样性可能比 Fd=0.1 掉得快但收敛更急;外汇与贵金属行情噪声大,这类算法实盘前务必用历史 tick 回测,杠杆品种高风险,参数过拟合概率不低。

MQL5 / C++
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_CRO::Depredation()
{
  class=class="str">"cmt">// Apply destruction with Pd probability
  if (u.RNDfromCI(class="num">0, class="num">1) < Pd)
  {
    class=class="str">"cmt">// Find all occupied positions and their indices
    class="type">int occupiedIndices [];
    class="type">int occupiedCount = class="num">0;
    for (class="type">int i = class="num">0; i < totalReefSize; i++)
    {
      if (occupied [i])
      {
        ArrayResize(occupiedIndices, occupiedCount + class="num">1);
        occupiedIndices [occupiedCount] = i;
        occupiedCount++;
      }
    }
    class=class="str">"cmt">// If there are no occupied positions, exit
    if (occupiedCount == class="num">0) class="kw">return;
    class=class="str">"cmt">// Sort the indices by fitness(from best to worst)
    SortAgentsByFitness(occupiedIndices, occupiedCount);
    class=class="str">"cmt">// Determine the number of best solutions used to generate new ones
    class="type">int eliteCount = (class="type">int)MathRound(class="num">0.1 * occupiedCount); class=class="str">"cmt">// Use top class="num">10%
    if (eliteCount <= class="num">0) eliteCount = class="num">1; class=class="str">"cmt">// At least one elite solution
    if (eliteCount > occupiedCount) eliteCount = occupiedCount;
    class=class="str">"cmt">// Determine the number of worst solutions to be replaced
    class="type">int removeCount = (class="type">int)MathRound(Fd * occupiedCount);
    if (removeCount <= class="num">0) removeCount = class="num">1; class=class="str">"cmt">// At least one solution is replaced
    if (removeCount > occupiedCount - eliteCount) removeCount = occupiedCount - eliteCount; class=class="str">"cmt">// Do not remove elite solutions
    class=class="str">"cmt">// Remove the worst solutions and replace them with new ones in the vicinity of the best ones
    for (class="type">int i = class="num">0; i < removeCount; i++)
    {
      class=class="str">"cmt">// Index of the solution to be removed(from the end of the sorted array)
      class="type">int removeIndex = occupiedCount - class="num">1 - i;
      if (removeIndex < class="num">0 || removeIndex >= occupiedCount) class="kw">continue;
      class="type">int posToRemove = occupiedIndices [removeIndex];

◍ 珊瑚礁算法里按幂律撒新解

这段逻辑来自一种受珊瑚礁启发的群体优化思路:先随机挑一个待清除的位置,越界就跳过;再从精英集合里按幂律偏好选一个优解当母体。power 设成 0.1 时,偏离量 deviation 会明显偏向大步长,新解更可能在远离当前最优的邻域生成,而非只做微调。 半径取各维度范围的 0.7 倍(注释写 10% 属笔误,实际系数是 0.7),符号随机正反,最终用 MathPow(rnd, 1/power) 放大随机值。对 XAUUSD 这类跳点凶的品种,这种生成法可能比纯高斯扰动更快跳出局部劣解,但也可能在窄幅震荡时浪费评估次数。 下面把核心片段拆开看:

if(posToRemove<0posToRemove>=totalReefSize) continue; // 待删位置非法就跳过

// Choose one of the elite solutions double power=0.1; // 幂律参数,越小长尾越重 double r=u.RNDfromCI(0,1); int eliteIdx=(int)(eliteCount); if(eliteIdx>=eliteCount) eliteIdx=eliteCount-1; // 防止越界取最后一个精英 int posBest=occupiedIndices[eliteIdx];

if(posBest<0posBest>=totalReefSize) continue;

int bestAgentIdx=reefIndices[posBest];

if(bestAgentIdx<0bestAgentIdx>=popSize) continue;

occupied[posToRemove]=false; // 腾出坑位 int newAgentIdx=reefIndices[posToRemove]; if(newAgentIdx>=0&&newAgentIdx<popSize) { for(int c=0;c<coords;c++) { double radius=0.7*(rangeMax[c]-rangeMin[c]); // 实际半径=70%区间宽 double sign=u.RNDfromCI(0,1)<0.5?-1.0:1.0; // 随机方向 double deviation=sign*radius*MathPow(u.RNDfromCI(0,1),1.0/power); // 幂律拉伸 double newValue=a[bestAgentIdx].c[c]+deviation; a[newAgentIdx].c[c]=u.SeInDiSp(newValue,rangeMin[c],rangeMax[c],rangeStep[c]); // 夹回可行域 } occupied[posToRemove]=true; reefIndices[posToRemove]=newAgentIdx; } 在 MT5 里把 power 从 0.1 调到 0.3 跑同一段,能直接对比长尾厚度对收敛速度的影响;外汇与贵金属杠杆高、回撤快,任何参数优化都先用历史数据小样本验证再上实盘。

MQL5 / C++
if(posToRemove<class="num">0||posToRemove>=totalReefSize) class="kw">continue;
class=class="str">"cmt">// Choose one of the elite solutions
class="type">class="kw">double power=class="num">0.1; class=class="str">"cmt">// Power distribution parameter
class="type">class="kw">double r=u.RNDfromCI(class="num">0,class="num">1);
class="type">int eliteIdx=(class="type">int)(eliteCount);
if(eliteIdx>=eliteCount) eliteIdx=eliteCount-class="num">1;
class="type">int posBest=occupiedIndices[eliteIdx];
if(posBest<class="num">0||posBest>=totalReefSize) class="kw">continue;
class="type">int bestAgentIdx=reefIndices[posBest];
if(bestAgentIdx<class="num">0||bestAgentIdx>=popSize) class="kw">continue;
class=class="str">"cmt">// Free up space for a new solution
occupied[posToRemove]=class="kw">false;
class=class="str">"cmt">// Generate a new solution in the neighborhood of the selected elite solution
class="type">int newAgentIdx=reefIndices[posToRemove]; class=class="str">"cmt">// Use the same agent index
if(newAgentIdx>=class="num">0&&newAgentIdx<popSize)
{
 class=class="str">"cmt">// Generation via power-law distribution around the best solution
 for(class="type">int c=class="num">0;c<coords;c++)
 {
  class=class="str">"cmt">// Determine the search radius(can be adapted depending on progress)
  class="type">class="kw">double radius=class="num">0.7*(rangeMax[c]-rangeMin[c]); class=class="str">"cmt">// class="num">10% of the range
  class=class="str">"cmt">// Generate a value class="kw">using a power law
  class="type">class="kw">double sign=u.RNDfromCI(class="num">0,class="num">1)<class="num">0.5?-class="num">1.0:class="num">1.0; class=class="str">"cmt">// Random sign
  class="type">class="kw">double deviation=sign*radius*MathPow(u.RNDfromCI(class="num">0,class="num">1),class="num">1.0/power);
  class=class="str">"cmt">// New value in the neighborhood of the best one
  class="type">class="kw">double newValue=a[bestAgentIdx].c[c]+deviation;
  class=class="str">"cmt">// Limit the value to an acceptable range
  a[newAgentIdx].c[c]=u.SeInDiSp(newValue,rangeMin[c],rangeMax[c],rangeStep[c]);
 }
 class=class="str">"cmt">// Populate the new solution into the reef
 occupied[posToRemove]=true;
 reefIndices[posToRemove]=newAgentIdx;
}

「画得少,看得清」

CROm 把珊瑚礁的演替逻辑压成了几条硬规则:只围绕精英解撒新解,用幂参数 power=0.1 的逆幂律把大多数扰动锁在最优解附近,偶尔放一个大幅偏离去探远水。搜索半径拉到可行域 70% 后,局部利用和全局探索的配比不再靠拍脑袋。 测试台给的评分区间是 0–100,100 仅是理论上限;附带的 CalculationTestResults.mqh 能直接算出排名表,读者开 MT5 跑 Test_AO_CROm.mq5 就能复现直方图里的分布。离散函数上它掉链子,外部参数也偏多,这两点决定它不适合直接搬去调参 EA 的离散开关。 作者把广播产卵、幼体定居这类算子开源在 github 的 Population-optimization-algorithms-MQL5 库里,想写自己的群体算法,抄这几类算子比从零起步省事。外汇与贵金属市场高杠杆、高波动,任何优化器给出的参数集都只是概率优势,上线前务必用历史数据压力测试。

把重复跑分交给小布盯盘
这些基准函数的批量回测与收敛曲线诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到,你只需判断参数结构是否匹配自己的交易窗口。

常见问题

逆幂律让新解更倾向在已得优解邻域生成,保留全局探索的同时加快局部锁定,对多模态复杂空间更稳。
网格规模抬升会增加空闲单元竞争与幼虫附着开销,倾向在问题维度附近做折中,而非盲目扩网格。
可以,小布盯盘支持导入外部元启发式输出的参数集并自动比对历史品种波动,省去手动录参的重复劳动。
单峰空间本就少局部陷阱,传统 CRO 已够用,逆幂律机制的增益主要在多峰与崎岖搜索面才显现。
Fe 偏高利于交叉广搜,偏低强化变异细探;复杂约束下可先做小规模扫描,再向 0.5 附近收敛调试。