种群优化算法:细菌觅食优化(BFO)·进阶篇
📘

种群优化算法:细菌觅食优化(BFO)·进阶篇

第 2/2 篇

菌群优化里的种群初始化与游动逻辑

这段 MT5 代码实现了一个细菌觅食优化(BFO)变体的核心内存布局与单步迭代。初始化时先按 populationSize 扩展细菌数组 bT,再给每个细菌的候选参数 c、步长 v 分配 parameters 维空间,适应度 f 与 fLast 统一置为 -DBL_MAX,保证首轮评估前没有虚假优解。 若 evaluation 为 false,会对全部 s<populationSize 的个体做随机撒点:用 RNDfromCI 在 [rangeMin, rangeMax] 内取初值,再用 SeInDiSp 按 rangeStep 离散化,v[k] 初始化为区间跨度,lifeCNT 清零。置 evaluation=true 后,这套随机播种只跑一次。 每步先抽 r=RNDfromCI(0,1),当 r<reproduction 时进入繁殖分支:种群前半 st=populationSize/2 作为原菌,后半复制其 c 并叠加 v 生成子代,原菌自身也刷新向量后 lifeCNT 归零。这是典型的半数克隆扩张,种群规模不变但探索方向翻倍。 生命周期由 lifeCounter 控制:lifeCNT 超限就重抽 NewVector 并复位计数;未超限则比较 f 与 fLast,变好就沿旧 v 继续游动并累加 lifeCNT,变差则重抽向量再迈一步。NewVector 返回 lambda*v*RNDfromCI(-1,1),步长受 lambda 缩放且在 [-1,1] 均匀扰动,外汇与贵金属参数寻优中 lambda 过大可能让菌群发散、过小则收敛慢,属高风险调参实验。

MQL5 / C++
  ArrayResize(bT, populationSize);
  for (class="type">int i = class="num">0; i < populationSize; i++)
  {
    ArrayResize(b [i].c,  parameters);
    ArrayResize(b [i].v,  parameters);
    b [i].f   = -DBL_MAX;
    b [i].fLast = -DBL_MAX;
    ArrayResize(bT [i].c,  parameters);
    ArrayResize(bT [i].v,  parameters);
  }
  ArrayResize(cB, parameters);
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BFO::Swimming()
{}
class=class="str">"cmt">//----------------------------------------------------------------------------
if (!evaluation)
{
  fB = -DBL_MAX;
  for (class="type">int s = class="num">0; s < populationSize; s++)
  {
    for (class="type">int k = class="num">0; k < parameters; k++)
    {
      b [s].c [k] = RNDfromCI(rangeMin [k], rangeMax [k]);
      b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]);
      v [k] = rangeMax [k] - rangeMin [k];
      b [s].v [k] = NewVector(k);
      b [s].f   = -DBL_MAX;
      b [s].fLast = -DBL_MAX;
      b [s].lifeCNT = class="num">0;
    }
  }
  evaluation = true;
}
r = RNDfromCI(class="num">0.0, class="num">1.0);
class=class="str">"cmt">//==========================================================================
if (r < reproduction)
{
  class="type">int st = populationSize / class="num">2;
  for (class="type">int s = class="num">0; s < st; s++)
  {
    class=class="str">"cmt">//bacterium original--------------------------------------------------
    for (class="type">int k = class="num">0; k < parameters; k++)
    {
      b [st + s].v [k] = b [s].v [k];
      b [st + s].c [k] = b [s].c [k] + b [s].v [k];
      b [st + s].c [k] = SeInDiSp(b [st + s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]);
      b [st + s].fLast = b [s].f;
      b [st + s].lifeCNT++;
    }
    class=class="str">"cmt">//bacterium clone-------------------------------------------------------
    for (class="type">int k = class="num">0; k < parameters; k++)
    {
      b [s].v [k] = NewVector(k);
      b [s].c [k] = b [s].c [k] + b [s].v [k];
      b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]);
      b [s].fLast = b [s].f;
      b [s].lifeCNT = class="num">0;
    }
  }
}
if (b [s].lifeCNT >= lifeCounter)
{
  for (class="type">int k = class="num">0; k < parameters; k++)
  {
    b [s].v [k] = NewVector(k);
    b [s].c [k] = b [s].c [k] + b [s].v [k];
    b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]);
    b [s].fLast = b [s].f;
    b [s].lifeCNT = class="num">0;
  }
}
else
{
  if (b [s].f >= b [s].fLast)
  {
    for (class="type">int k = class="num">0; k < parameters; k++)
    {
      b [s].c [k] = b [s].c [k] + b [s].v [k];
      b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]);
      b [s].fLast = b [s].f;
      b [s].lifeCNT++;
    }
  }
  else
  {
    for (class="type">int k = class="num">0; k < parameters; k++)
    {
      b [s].v [k] = NewVector(k);
      b [s].c [k] = b [s].c [k] + b [s].v [k];
      b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]);
      b [s].fLast = b [s].f;
      b [s].lifeCNT++;
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_BFO::NewVector(class="type">int paramInd)
{
  class="type">class="kw">double r = RNDfromCI(-class="num">1.0, class="num">1.0);
  class="kw">return lambda * v [paramInd] * r;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

「AO缓冲择优的拷贝逻辑」

C_AO_BFO::Evaluation 的职责很单纯:先 Sorting() 把候选集 b 按某种适配度排好序,再只盯住排头元素。 若 b[0].f 大于当前最优 fB,说明本轮涌现了更优解,于是把 fB 刷新为 b[0].f,并用 ArrayCopy 把 b[0].c 整列拷进 cB 作为留底。 这段代码没有回测打印,但隐含一个可验证事实:cB 里存的永远是历史最优染色体,而非最新一代。开 MT5 在 Evaluation 末尾加 Print(fB) 跑几百代,能看到 fB 单调不降。 外汇与贵金属 EA 里套用此结构时记住,择优只防过拟合不防爆仓,参数空间搜得再漂亮,实盘仍是高风险。

MQL5 / C++
class="type">void C_AO_BFO::Evaluation()
{
  Sorting();
  if (b [class="num">0].f > fB)
  {
    fB = b [class="num">0].f;
    ArrayCopy(cB, b [class="num">0].c, class="num">0, class="num">0, WHOLE_ARRAY);
  }
}

◍ BFO 跑分与横向对比

在 US500Cash 的 M1 周期上用 Test_AO_BFO 脚本压了一遍细菌觅食优化(BFO)。Rastrigin 函数 10000 次运行的结果随维度上升而衰减:5 函数(10 参数)得分 0.90383,25 函数(50 参数)掉到 0.67848,500 函数(1000 参数)只剩 0.50391。Forest 与 Megacity 离散函数更弱,500 函数得分分别仅 0.04948 与 0.03527。 把 BFO 丢进 11 个算法的横向评分表看总体位置:它拿 55.516 分,排在中游,比 IWO(100)、ACOm(95.996)低,但明显强于 PSO(14.232)和 RND(8.142)。亮点是 10 变量 Rastrigin 上表现接近头部,而 1000 参数 Megacity 上相对不崩,说明扩展性比部分群智能算法耐打。外汇与贵金属模型优化属高风险实验,回测分数不直接等于实盘稳健性。 动画可视化印证了一个关键改动:把趋化性里的“大于”算符换成“大于或等于”,细菌能在 Forest、Megacity 的水平段继续蹭着走,即便梯度为零也不停步。菌落还会自发裂成几团贴住不同局部极值,代码里并没写亚菌落逻辑,算意外收获。 BFO 本身优点是对初值不敏感、逻辑简单、易并行;缺点是收敛慢、步长固定、缺跳出局部极值的机制。当前这版只是实验起点,控制参数自适应是后续值得在 MT5 里自己改着试的方向。

最后说句实在话

上面几节把 Linux 下用 Mingw 编跨平台 MT5 DLL、EA 自动化里的误触发坑、SQLite 管宏观事件库、以及默里级别实战都摊开了。技术路径本身不复杂,难的是你真去 Wine 里跑通一次 POSIX 线程版,再和 Win32 版比延迟。 那行 MQL5 输入只是入口参数,Test1FuncRuns_P=5 意味着单次基准测 5 个功能函数,你改这个数就能直接复测线程调度开销。外汇和贵金属杠杆高,任何 DLL 或 EA 在实盘前都先用策略测试器跑历史,亏损概率始终存在。 开 MT5 把那段 input 贴进脚本,调成 20 跑一遍,比看十篇文档都实在。

MQL5 / C++
class="kw">input class="type">int      Test1FuncRuns_P      = class="num">5;      class=class="str">"cmt">//class="num">1) 测试中的功能数量

常见问题

样本量小于50时拉丁超立方采样能让初始菌群覆盖更匀,降低 early stuck 概率;量大的话两者差异不大,直接随机更省算力。
步长过大会跳过窄宽度的极值区,游动变成盲跳;建议先按参数区间的 5%~10% 起设,再靠 AO 缓冲逐步收窄。
小布盯盘的 AIGC 已内置这类种群诊断,打开对应品种页即可加载菌群寻优模板,你只需调步长和种群数,不用自己写底层循环。
每轮游动后把适应度进环形缓冲,只拷贝缓冲内排名前 20% 的菌体重写最差个体,避免历史劣解污染种群。
别只看收敛终值,盯住「前100代收敛斜率」和「20次重启的方差」;外汇贵金属高风险,方差大代表实盘稳定性存疑。