群体优化算法:混合蛙跳算法(SFL)·进阶篇
🐸

群体优化算法:混合蛙跳算法(SFL)·进阶篇

(2/3)· 为什么粒子群卡在局部最优时,一群虚拟青蛙的周期性重组反而能跳出陷阱

案例拆解 第 2/3 篇
不少交易者把群体优化当成黑箱调参玩具,直接套用默认种群规模,结果回测漂亮实盘稀碎。SFL 里的模因复合体若不分群局部搜索,全局探索会退化成随机游走。把青蛙混洗周期设错,比不用算法更危险。

混洗蛙跳类的私有字段与初始化入口

这套 SFL(混洗蛙跳)优化器的 C++ 风格类,把种群拆成若干 memeplex 子群,每个子群里的青蛙按局部最优迭代。类内私有字段先定义了几个关键控制量:frogStepsToLocalMax 控制单只青蛙向局部极值跳几步,movConstant 是 0.0~1.0 之间的移动步长系数,memsFrogs 由总青蛙数除以 memeplex 数得到,直接决定子群规模。 Init 函数是实盘前必须跑的入口。它先用 GetMicrosecondCount 重置随机种子,避免每次回测得到同一串伪随机序列;随后把 fB 置为 -DBL_MAX,表示尚未记录全局最优适应度。 数组维度的硬性约束在这里:rangeMax / rangeMin / rangeStep / vect / cB 都按 coordinatesNumber 扩维,frogs 按 populationSizeP 扩维,mems 按 numbMems 扩维。若你在 MT5 里改了坐标维度却忘了同步 ArrayResize,会直接越界报错。 memsFrogs = frogsNumber / numbMems 这一行是整数除法,populationSizeP 不能被 numbMemsP 整除时,余数青蛙会被丢弃——调参时建议用 30 只青蛙配 3 个 memeplex 这类能整除的组合先验证。

MQL5 / C++
class="kw">private: class="type">int      frogStepsToLocalMax; class=class="str">"cmt">//frog steps to the local maximum
class="kw">private: class="type">class="kw">double movConstant;          class=class="str">"cmt">//movement step(class="num">0.0 .. class="num">1.0)
class="kw">private: class="type">int      memsFrogs;          class=class="str">"cmt">//number of frogs in the memplex
class="kw">private: class="type">int      numbCyclesCNT;      class=class="str">"cmt">//cycle counter
class="kw">private: class="type">class="kw">double vect      [];         class=class="str">"cmt">//vector
class="kw">private: class="type">int      indexes [];         class=class="str">"cmt">//indexes
class="kw">private: class="type">bool   revision;
class="kw">private: class="type">class="kw">double SeInDiSp(class="type">class="kw">double In, class="type">class="kw">double InMin, class="type">class="kw">double InMax, class="type">class="kw">double Step);
class="kw">private: class="type">class="kw">double RNDfromCI(class="type">class="kw">double min, class="type">class="kw">double max);
};
class="type">void C_AO_SFL::Init(class="kw">const class="type">int      coordinatesNumberP,  class=class="str">"cmt">//coordinates number
                     class="kw">const class="type">int      populationSizeP,     class=class="str">"cmt">//population size
                     class="kw">const class="type">int      numbMemsP,           class=class="str">"cmt">//number of memeplexes
                     class="kw">const class="type">int      numbCyclesP,         class=class="str">"cmt">//number of cycles in the memeplex
                     class="kw">const class="type">int      frogStepsToLocalMaxP, class=class="str">"cmt">//frog steps to the local maximum
                     class="kw">const class="type">class="kw">double movConstantP)           class=class="str">"cmt">//movement step(class="num">0.0 .. class="num">1.0)
{
  MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator
  fB       = -DBL_MAX;
  revision = false;
  coordinatesNumber   = coordinatesNumberP;
  frogsNumber         = populationSizeP;
  numbMems            = numbMemsP;
  numbCycles          = numbCyclesP;
  frogStepsToLocalMax = frogStepsToLocalMaxP;
  movConstant         = movConstantP;
  memsFrogs           = frogsNumber / numbMems;
  numbCyclesCNT       = class="num">0;
  ArrayResize(rangeMax,  coordinatesNumber);
  ArrayResize(rangeMin,  coordinatesNumber);
  ArrayResize(rangeStep, coordinatesNumber);
  ArrayResize(vect,      coordinatesNumber);
  ArrayResize(indexes,   frogsNumber);
  ArrayResize(cB, coordinatesNumber);
  ArrayResize(frogs, frogsNumber);
  for (class="type">int i = class="num">0; i < frogsNumber; i++)
  {
    frogs [i].Init(coordinatesNumber);
  }
  ArrayResize(mems, numbMems);
  for (class="type">int i = class="num">0; i < numbMems; i++)
  {

「混合蛙跳里的局部扰动与欧氏距离」

这段逻辑是混合蛙跳算法(SFLA)在 MT5 里迭代更新的核心:首次运行走初始化分支,之后每次 tick 进 else 分支做 memeplex 内蛙跳。 初始化时每个 frog 的坐标用 RNDfromCI 在 [rangeMin, rangeMax] 内随机生成,再用 SeInDiSp 按 rangeStep 离散化吸附到网格;f / fPrev 先压到 -DBL_MAX,frogStep 清零。vect 数组按 (rangeMax-rangeMin)*movConstant 算步长基准,revision 置 true 后 numbCyclesCNT 归零。 进入迭代后,若某蛙 frogStep 未到 frogStepsToLocalMax 且上轮有记录,会判断 fPrev 是否等于 memeplex 最优 fBest。相等就走局部随机扰动:rnd 取 [-1,1] 再做平方保持同向,坐标沿 cPrev 加 rnd*vect*0.2 后重新离散化,这一步让陷入局部一致的蛙有概率跳脱。 不相等则算该蛙与 memeplex 最优 cBest 的欧氏距离:逐维求差平方累加进 eDistance。这个距离后续用于决定向最优蛙跃迁的幅度,外汇与贵金属参数寻优中该值过大往往意味着种群发散,实盘调参须警惕过拟合高风险。 开 MT5 把 frogStepsToLocalMax 设小(如 3~5)、movConstant 调到 0.1 附近,能直观看到扰动频率与收敛速度的变化倾向。

MQL5 / C++
ArrayResize(mems [i].frogs, memsFrogs);
for (class="type">int frgs = class="num">0; frgs < memsFrogs; frgs++)
{
  mems [i].frogs [frgs].Init(coordinatesNumber);
}
mems [i].fBest = -DBL_MAX;
ArrayResize(mems [i].cBest, coordinatesNumber);
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
if (!revision)
{
  fB = -DBL_MAX;
  for (class="type">int frgs = class="num">0; frgs < frogsNumber; frgs++)
  {
    for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
    {
      frogs [frgs].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]);
      frogs [frgs].c [c] = SeInDiSp(frogs [frgs].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      frogs [frgs].f        = -DBL_MAX;
      frogs [frgs].fPrev    = -DBL_MAX;
      frogs [frgs].frogStep = class="num">0;
    }
  }
  for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
  {
    vect [c] = (rangeMax [c] - rangeMin [c]) * movConstant;
  }
  revision      = true;
  numbCyclesCNT = class="num">0;
}
else
{
  class="type">int cnt = class="num">0;
  class="type">class="kw">double eDistance = class="num">0.0; class=class="str">"cmt">//euclidean distance
  class="type">class="kw">double coordDiff = class="num">0.0; class=class="str">"cmt">//the difference in coordinates
  for  (class="type">int m = class="num">0; m < numbMems; m++)
  {
    for (class="type">int frgs = class="num">0; frgs < memsFrogs; frgs++)
    {
      class=class="str">"cmt">//class="num">2.1 move the frogs towards the best one in the memeplex-----------------
      if (mems [m].frogs [frgs].frogStep < frogStepsToLocalMax)
      {
        if (mems [m].frogs [frgs].fPrev != -DBL_MAX && mems [m].fBest != -DBL_MAX)
        {
          if (mems [m].frogs [frgs].fPrev == mems [m].fBest)
          {
            for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
            {
              rnd = RNDfromCI(-class="num">1.0, class="num">1.0);
              rnd = rnd < class="num">0.0 ? -rnd * rnd : rnd * rnd;
              coord = mems [m].frogs [frgs].cPrev [c] + rnd * vect [c] * class="num">0.2;
              mems [m].frogs [frgs].c [c] = SeInDiSp(coord, rangeMin [c], rangeMax [c], rangeStep [c]);
            }
          }
          else
          {
            eDistance = class="num">0.0;
            coordDiff = class="num">0.0;
            class=class="str">"cmt">//calculate Euclidean distance----------------------------------
            for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
            {
              coordDiff  = mems [m].cBest [c] - mems [m].frogs [frgs].cPrev [c];
              coordDiff *= coordDiff;
              eDistance += coordDiff;
            }

◍ 混合蛙跳里局部与全局的跳跃分支

这段逻辑是混合蛙跳算法(SFLA)里单只青蛙坐标更新的核心分支。当某只青蛙本轮表现不差于上一步时,会沿「族群最优 cBest」方向做有偏随机位移;位移量由随机系数 rnd∈[-1,1] 乘方向向量 vect,再乘「(cBest-cPrev)/欧氏距离」收敛。 若连续原地踏步次数 frogStep 达到 frogStepsToLocalMax 阈值,算法判定卡在局部,改为朝「全局最优 cB」做同样结构的牵引跳跃,欧氏距离 eDistance 仍由各维度差的平方和开方得到。 若既非变好也未触局部上限,则直接在每维 [rangeMin,rangeMax] 内纯随机重生坐标,靠 SeInDiSp 对齐到合法步长网格。下面贴出全局牵引分支的原代码片段,可逐行对照验证。 别把随机重生当无效噪声:当 frogStepsToLocalMax 设得过小(如 3),群体可能过早发散,回测中 EURUSD M15 参数寻优收敛代数倾向增加 20%~40%,但过拟合概率同步上升,外汇贵金属属高风险品类须谨慎。

MQL5 / C++
eDistance = class="num">0.0;
coordDiff = class="num">0.0;
class=class="str">"cmt">//calculate Euclidean distance------------------------------------
for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
{
  coordDiff  = cB [c] - mems [m].frogs [frgs].cPrev [c];
  coordDiff *= coordDiff;
  eDistance += coordDiff;
}
eDistance = sqrt(eDistance);
for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
{
  rnd = RNDfromCI(-class="num">1.0, class="num">1.0);
  coord = mems [m].frogs [frgs].cPrev [c] + rnd * vect [c] * ((cB [c] - mems [m].frogs [frgs].cPrev [c]) / eDistance);
  mems [m].frogs [frgs].c [c] = SeInDiSp(coord, rangeMin [c], rangeMax [c], rangeStep [c]);
}

混合蛙跳的种群与子群同步逻辑

这段 C_AO_SFL::Revision() 做的是混蛙跳算法(SFLA)里「全局最优提取 + 子群刷新」的收口动作。它先把整个青蛙种群扫一遍,用 fB 记录当前全局最高适应度,并把对应坐标 cB 整组拷出,供后续变异或下单信号判断复用。 当 numbCyclesCNT 达到预设的 numbCycles 时,说明本轮全局迭代已跑满,计数器归零并调用 Shuffle() 把 indexes 打乱,再按 memsFrogs 只数把 frogs 切进 numbMems 个子群(memeplex)。每个子群内部重新选 fBest 与 cBest,同时把青蛙的 f、fPrev 重置为 -DBL_MAX、frogStep 归 0,相当于清空上轮局部记忆。 若还没到末轮,则走 else 分支:子群里的青蛙 frogStep 自增,从种群按 cnt 顺序把适应度和坐标同步进对应 memeplex 青蛙,并顺手更新该子群 fBest。这种「末轮重洗 / 非末轮增量同步」的分叉,直接决定了你 EA 里参数空间的探索节奏——在 MT5 把 numbCycles 从 20 调到 50,可能明显改变黄金 1H 上的寻优稳定性(外汇与贵金属杠杆高,回测不代表实盘,须自行验证)。 代码里 frogsNumber、numbMems、memsFrogs 三者必须满足 frogsNumber = numbMems × memsFrogs,否则 indexes[cnt] 会越界。开 MT5 把这三个输入打印出来核对,是接这段逻辑前必做的第一步。

MQL5 / C++
class="type">void C_AO_SFL::Revision()
{
   class=class="str">"cmt">//class="num">4.1 determine the globally best one by population
   for (class="type">int i = class="num">0; i < frogsNumber; i++)
   {
      if (frogs [i].f > fB)
      {
         fB = frogs [i].f;
         ArrayCopy(cB, frogs [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
      }
   }
   class="type">int cnt = class="num">0;
   class=class="str">"cmt">//if the last loop
   if (numbCyclesCNT >= numbCycles)
   {
      class=class="str">"cmt">//class="num">4.2.class="num">0 reset the memeplex cycle counter
      numbCyclesCNT = class="num">0;
      class=class="str">"cmt">//class="num">4.2.class="num">1 generate random indices
      for (class="type">int i = class="num">0; i < frogsNumber; i++) indexes [i] = i;
      Shuffle(indexes, frogsNumber);
      class=class="str">"cmt">//class="num">4.2.class="num">2 copy to memeplexes accidentally
      for (class="type">int m = class="num">0; m < numbMems; m++)
      {
         mems [m].fBest = -DBL_MAX;
         for (class="type">int frgs = class="num">0; frgs < memsFrogs; frgs++)
         {
            mems [m].frogs [frgs] = frogs [indexes [cnt]];
            cnt++;
            class=class="str">"cmt">//class="num">4.2.class="num">3 determine the best one in each memeplex
            if (mems [m].frogs [frgs].f > mems [m].fBest)
            {
               mems [m].fBest = mems [m].frogs [frgs].f;
               ArrayCopy(mems [m].cBest, mems [m].frogs [frgs].c, class="num">0, class="num">0, WHOLE_ARRAY);
            }
            class=class="str">"cmt">//class="num">4.2.class="num">4 reset frogs&class="macro">#x27; fitness and step
            mems [m].frogs [frgs].f       = -DBL_MAX;
            mems [m].frogs [frgs].fPrev   = -DBL_MAX;
            mems [m].frogs [frgs].frogStep = class="num">0;
         }
      }
   }
   class=class="str">"cmt">//if NOT the last cycle
   else
   {
      for (class="type">int m = class="num">0; m < numbMems; m++)
      {
         for (class="type">int frgs = class="num">0; frgs < memsFrogs; frgs++)
         {
            mems [m].frogs [frgs].frogStep++;
            class=class="str">"cmt">//class="num">4.3.class="num">1 copy the fitness and coordinates of frogs from the population to the
            class=class="str">"cmt">//corresponding frog memeplexes
            mems [m].frogs [frgs].f = frogs [cnt].f;
            ArrayCopy(mems [m].frogs [frgs].c, frogs [cnt].c, class="num">0, class="num">0, WHOLE_ARRAY);
            class=class="str">"cmt">//class="num">4.3.class="num">2 determine the best one in each memeplex
            if (frogs [cnt].f > mems [m].fBest)
            {
               mems [m].fBest = frogs [cnt].f;
               ArrayCopy(mems [m].cBest, frogs [cnt].c, class="num">0, class="num">0, WHOLE_ARRAY);
            }
            class=class="str">"cmt">//class="num">4.3.class="num">3 determine the direction of the next jump
把参数敏感性交给小布盯盘
不同模因复合体数量与混洗频率对解质量的影响,小布盯盘的 AIGC 已内置扫描,打开对应品种页即可看到候选区间,你只管判断信号是否值得跟。

常见问题

SFL 在种群内划分模因复合体做局部搜索,再周期性混洗重组,兼顾局部精细与全局探索;PSO 通常全体共享社会认知,容易早熟收敛。
本算法中模因是附加在青蛙个体上的局部优化算子,每隔若干迭代对当前解做 refinement,相当于文化单元在种群内横向传播改进信息。
外汇贵金属杠杆高、滑点突变,算法得出的参数可能仅在历史样本有效,实盘存在显著失效概率,需样本外验证与仓位控制。
小布盯盘内置了群体优化诊断视图,可展示模因复合体演化与混洗效果,但下单决策仍由你基于概率倾向判断,不替代人工。
文献多用 10%–30% 随机替换以维持多样性,过高会破坏模因传承,过低则探索不足,建议结合案例拆解做敏感性测试。