种群优化算法:蝙蝠算法(BA)·进阶篇
📘

种群优化算法:蝙蝠算法(BA)·进阶篇

第 2/3 篇

◍ 蝙蝠算法初始化与首飞逻辑

这段 C_AO_BA 类的构造函数把蝙蝠群优化的超参数一次性灌入对象:脉冲率上下限、响度范围、alpha 与 gamma 收敛系数,以及最大迭代次数 maxIterP 都被固化为成员。数组 rangeMax / rangeMin / rangeStep 按维度 params 扩容,bats 按 batsNumber 扩容,每只蝙蝠的 position、speed 均为 params 维,初值 fitness 设为 -DBL_MAX。 Flight() 是核心步进函数。首次调用时 firstFlight 为 false,会遍历每只蝙蝠、每个维度,用 RNDfromCI 在参数区间内随机撒点,再用 SeInDiSp 做离散空间对齐;frequency 在 MIN_FREQ~MAX_FREQ 间随机,initPulseRate 取 MIN_PULSE~MAX_PULSE/2,loudness 取 MAX_LOUDNESS/2~MAX_LOUDNESS。首飞结束后 firstFlight 置 true,后续 epoch 进入 else 分支做平均响度计算与位置更新。 开 MT5 把这段贴进 EA 的 include 类,把 batsNumber 设成 30、params 设成你的指标维度,能在策略测试器里直接观察首代种群分布是否覆盖全区间。外汇与贵金属杠杆高,回测优解实盘可能失效,参数需谨慎验证。

MQL5 / C++
const class="type">class="kw">double min_PULSE_P,
const class="type">class="kw">double max_PULSE_P,
const class="type">class="kw">double alpha_P,
const class="type">class="kw">double gamma_P,
const class="type">int    maxIterP)
{
  MathSrand(GetTickCount());
  fB = -DBL_MAX;
  params      = paramsP;
  batsNumber  = batsNumberP;
  MIN_FREQ    = min_FREQ_P;
  MAX_FREQ    = max_FREQ_P;
  MIN_LOUDNESS = min_LOUDNESS_P;
  MAX_LOUDNESS = max_LOUDNESS_P;
  MIN_PULSE   = min_PULSE_P;
  MAX_PULSE   = max_PULSE_P;
  ALPHA       = alpha_P;
  GAMMA       = gamma_P;
  maxIter     = maxIterP;
  ArrayResize(rangeMax,  params);
  ArrayResize(rangeMin,  params);
  ArrayResize(rangeStep, params);
  firstFlight = false;
  ArrayResize(bats, batsNumber);
  for (class="type">int i = class="num">0; i < batsNumber; i++)
  {
    ArrayResize(bats [i].position,   params);
    ArrayResize(bats [i].auxPosition, params);
    ArrayResize(bats [i].speed,      params);
    bats [i].fitness  = -DBL_MAX;
  }
  ArrayResize(cB, params);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::Flight(class="type">int epoch)
{
  currentIteration = epoch;
  class=class="str">"cmt">//============================================================================
  if (!firstFlight)
  {
    fB = -DBL_MAX;
    class=class="str">"cmt">//--------------------------------------------------------------------------
    for (class="type">int b = class="num">0; b < batsNumber; b++)
    {
      for (class="type">int p = class="num">0; p < params; p++)
      {
        bats [b].position     [p] = RNDfromCI(rangeMin [p], rangeMax [p]);
        bats [b].position     [p] = SeInDiSp(bats [b].position [p], rangeMin [p], rangeMax [p], rangeStep [p]);
        bats [b].auxPosition [p] = bats [b].position     [p];
        bats [b].speed        [p] = class="num">0.0;
        bats [b].frequency         = RNDfromCI(MIN_FREQ, MAX_FREQ);
        bats [b].initPulseRate     = RNDfromCI(MIN_PULSE, MAX_PULSE / class="num">2);
        bats [b].pulseRate         = bats [b].initPulseRate;
        bats [b].loudness          = RNDfromCI(MAX_LOUDNESS / class="num">2, MAX_LOUDNESS);
        bats [b].fitness           = -DBL_MAX;
        bats [b].fitnessBest       = -DBL_MAX;
      }
    }
    firstFlight = true;
  }
  class=class="str">"cmt">//============================================================================
  else
  {
    class="type">class="kw">double avgLoudness = class="num">0;
    for (class="type">int b = class="num">0; b < batsNumber; b++)

「蝙蝠算法里的位置更新与接收判据」

这段实现把蝙蝠群优化(BA)的核心迭代拆成了几个内联方法,直接挂在 C_AO_BA 类下,跑在 MT5 的策略优化或自定义指标计算线程里。 先说 Walk:每只蝙蝠按频率调速度,频率在 MIN_FREQ 到 MAX_FREQ 之间均匀随机;速度增量正比于「自身位置减当前全局最优 cB」,再叠到位置上生成 auxPosition,最后用 SeInDiSp 把越界值拉回 [rangeMin, rangeMax] 并按 rangeStep 离散化。 AproxBest 只在脉冲随机值大于该蝙蝠 pulseRate 时触发,用平均响度 averageLoudness 乘 [-1,1] 随机量去扰动全局最优附近,同样走离散化约束——这是局部开采的逻辑。 Preparation 做两件事:先扫一遍把 fitness 最高的蝙蝠写进 cB;再对「随机响度低于自身 loudness 且 fitness 不低于历史最优」的蝙蝠调用 AcceptNewSolutions。 AcceptNewSolutions 里 loudness 乘 ALPHA 衰减,pulseRate 走 initPulseRate*(1-exp(-GAMMA*iter)),iter 由当前代数经 Scale 映射到 0~10。外汇与贵金属参数寻优用这套高风险,回测过拟合概率偏高,建议开 MT5 把 ALPHA、GAMMA 设为输入变量做敏感性验证。

MQL5 / C++
  {
      avgLoudness += bats [b].loudness;
    }
    avgLoudness /= batsNumber;
    for (class="type">int b = class="num">0; b < batsNumber; b++)
    {
      Walk(bats [b]);
      if (RNDfromCI(MIN_PULSE, MAX_PULSE) > bats [b].pulseRate)
      {
        AproxBest(bats [b], avgLoudness);
      }
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::Preparation()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  for (class="type">int b = class="num">0; b < batsNumber; b++)
  {
    if (bats [b].fitness > fB)
    {
      fB = bats [b].fitness;
      ArrayCopy(cB, bats [b].auxPosition, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  for (class="type">int b = class="num">0; b < batsNumber; b++)
  {
    if (RNDfromCI(MIN_LOUDNESS, MAX_LOUDNESS) < bats [b].loudness && bats [b].fitness >= bats [b].fitnessBest)
    {
      AcceptNewSolutions(bats [b]);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::Walk(S_Bat &bat)
{
  for (class="type">int j = class="num">0; j < params; ++j)
  {
    bat.frequency       = MIN_FREQ + (MAX_FREQ - MIN_FREQ) * RNDfromCI(class="num">0.0, class="num">1.0);
    bat.speed     [j] = bat.speed [j] + (bat.position [j] - cB [j]) * bat.frequency;
    bat.auxPosition [j] = bat.position [j] + bat.speed [j];
    bat.auxPosition [j] = SeInDiSp(bat.auxPosition [j], rangeMin [j], rangeMax [j], rangeStep [j]);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::AproxBest(S_Bat &bat, class="type">class="kw">double averageLoudness)
{
  for (class="type">int j = class="num">0; j < params; ++j)
  {
    bat.auxPosition [j] = cB [j] + averageLoudness * RNDfromCI(-class="num">1.0, class="num">1.0);
    bat.auxPosition [j] = SeInDiSp(bat.auxPosition [j], rangeMin [j], rangeMax [j], rangeStep [j]);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::AcceptNewSolutions(S_Bat &bat)
{
  ArrayCopy(bat.position, bat.auxPosition, class="num">0, class="num">0, WHOLE_ARRAY);
  bat.fitnessBest = bat.fitness;
  bat.loudness    = ALPHA * bat.loudness;
  class="type">class="kw">double iter     = Scale(currentIteration, class="num">1, maxIter, class="num">0.0, class="num">10.0, false);
  bat.pulseRate   = bat.initPulseRate *(class="num">1.0 - exp(-GAMMA * iter));
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

优化算法跑分台:BA 在平滑面吃香,离散面露怯

这一轮把测试台改了三处:变量维度拉到 10 / 50 / 1000;用 Rastrigin 替掉原来的 Skin 函数(平滑多极值,中心全局最小、四角全局最大);评分改成相对归一——同表最佳 1.0、最差 0.0,再放大成 1~100 的互比分。这样能直接看算法在不同复杂度函数上的真实落差。 蝙蝠算法(BA)在 Rastrigin 上表现抢眼:50 维和 1000 维都拿了该函数的头名,且变量越多结果越稳,说明它对平滑面的可扩展性不错。但在 Forest、Megacity 这类离散/裂隙函数上,BA 容易把坐标群困在局部高原,缺乏类似 COA 的 Levy 飞行跃迁机制,陷入局部极值的概率偏高。 最终评级里 ACOm 以 100 分居首(九测五优),COAm 78.177、FAm 74.486 紧随,BA 自身 62.962。BA 的硬伤是参数太多且各参数对收敛性质影响极大,新手调参后结果可能天差地别,经验不足者不建议直接上。 下面这段是 BA 测试脚本在 EURUSD M1 上实跑 Print 出来的片段,能直接抄进 MT5 脚本验证: C_AO_BA:50;0.0;1.0;0.0;1.5;0.0;1.0;0.3;0.3 是初始化参数行;随后 5/25/500 维 Rastrigin 各跑 10000 次,Score 分别为 0.82562 / 0.81175 / 0.74151,随维度上升而下滑;5 维 Forest 的 Score 仅 0.33156,印证离散面弱势。外汇与贵金属优化属高风险,回测分高不代表实盘能复现。

MQL5 / C++
class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">46.384      Test_AO_BA(EURUSD,M1)      C_AO_BA:class="num">50;class="num">0.0;class="num">1.0;class="num">0.0;class="num">1.5;class="num">0.0;class="num">1.0;class="num">0.3;class="num">0.3
class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">46.384      Test_AO_BA(EURUSD,M1)      =============================
class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">48.451      Test_AO_BA(EURUSD,M1)      class="num">5 Rastrigin&class="macro">#x27;s; Func runs class="num">10000 result: class="num">66.63334336098077
class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">48.451      Test_AO_BA(EURUSD,M1)      Score: class="num">0.82562
class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">52.630      Test_AO_BA(EURUSD,M1)      class="num">25 Rastrigin&class="macro">#x27;s; Func runs class="num">10000 result: class="num">65.51391114042588
class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">52.630      Test_AO_BA(EURUSD,M1)      Score: class="num">0.81175
class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">27.234      Test_AO_BA(EURUSD,M1)      class="num">500 Rastrigin&class="macro">#x27;s; Func runs class="num">10000 result: class="num">59.84512760590815
class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">27.234      Test_AO_BA(EURUSD,M1)      Score: class="num">0.74151
class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">27.234      Test_AO_BA(EURUSD,M1)      =============================
class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">32.280      Test_AO_BA(EURUSD,M1)      class="num">5 Forest&class="macro">#x27;s; Func runs class="num">10000 result: class="num">0.5861602092218606
class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">32.280      Test_AO_BA(EURUSD,M1)      Score: class="num">0.33156

◍ 森林与都市模型在 EURUSD M1 上的跑分对照

同一套测试脚本在 EURUSD M1 周期下,分别用 Forest 和 Megacity 两种结构跑了 10000 次函数调用。Forest 在 25 棵树时 Func runs 结果 0.2896、Score 0.16379;加到 500 棵树反而掉到 0.0987、Score 0.05582,过拟合倾向明显。 Megacity 这边 5 个单位时 Func runs 高达 3.32、Score 0.27667,随单位数上升到 500 回落到 0.4076、Score 0.03397。小体量结构在 M1 噪声里更灵敏,堆量之后边际收益递减。

  • 01.03 的 C_AO 三组对照里,RND 汇总值 0.6893,PSO 汇总值 1.0530,ACOm 前两组全 1.0。ACOm 在样本上几乎吃满分数,但实盘外汇/贵金属高风险,这种饱和分数往往代表回测环境被特定形态主导,直接跟单可能亏在滑点和点差。

开 MT5 把 Test_AO_BA 的 Forest/Megacity 数量参数从 5 拉到 500 逐档跑一遍,重点看 Score 随规模的非单调变化,比盯绝对数值更有用。

常见问题

原文跑分显示 30~50 只个体在森林与都市模型下稳定性较好,少于 20 只易陷局部最优,可先试 40。
建议 A0 取 0.9~1.0、r0 取 0.1~0.3,原文首飞阶段靠高响度保探索,低脉冲率控局部细搜。
可以,小布已内置森林与都市模型的对照诊断,打开 EURUSD M1 品种页即可看到 BA 平滑面与离散面的表现差异。
原文跑分台指出 BA 依赖连续梯度近似更新位置,离散面跳变多导致接收判据频繁失效,建议换离散变种或混合算法。
看随机数是否小于当前脉冲率 r,以及新解目标值是否优于种群当前最佳,两者满足才接收并更新最佳。