种群优化算法:鸟群算法(BSA)·进阶篇
📘

种群优化算法:鸟群算法(BSA)·进阶篇

第 2/2 篇

◍ 鸟群算法代理类的参数与接口

这套鸟群搜索(BSA)代理类用 10 个外部参数驱动群体行为,从飞行概率到生产者/乞食者权重都在 params 数组里读取,索引 1 到 10 分别对应 flyingProb、producerProb、foragingProb、a1、a2、C、S、FL、producerPower、scroungerPower。 a1、a2、FL 三个常量被限定在 [0...2] 区间,C 与 S 是认知/社会系数,直接影响个体向历史最优与群体均值靠拢的拉力。生产者和乞食者概率则决定每次迭代中两种角色的分布比例。 Init 接口接收三组区间数组(最小/最大/步长)加可选 epoch 数,Moving、Revision、Injection 分别负责位移、参数修订与单点注入。私有段里的 mean[] 存全群平均位置,BirdProducer / BirdScrounger / BirdForaging / BirdVigilance 是四类微观行为的具体实现。 在 MT5 里把这类代理接进 EA 时,先确认 params[4]~params[6] 不要越出注释标定的常数域,否则群体可能过早收敛或发散。外汇与贵金属市场波动剧烈,任何基于群智能的仓位信号都只是概率倾向,实盘前务必用历史数据做样本外验证。

MQL5 / C++
flyingProb      = params [class="num">1].val;
producerProb    = params [class="num">2].val;
foragingProb    = params [class="num">3].val;
a1              = params [class="num">4].val;
a2              = params [class="num">5].val;
C               = params [class="num">6].val;
S               = params [class="num">7].val;
FL              = params [class="num">8].val;
producerPower   = params [class="num">9].val;
scroungerPower  = params [class="num">10].val;
}
class="type">bool Init(const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range
           const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">//maximum search range
           const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search
           const class="type">int    epochsP = class="num">0);   class=class="str">"cmt">//number of epochs
class="type">void Moving();
class="type">void Revision();
class="type">void Injection(const class="type">int popPos, const class="type">int coordPos, const class="type">class="kw">double value);
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double flyingProb;       class=class="str">"cmt">//Flight probability
class="type">class="kw">double producerProb;     class=class="str">"cmt">//Producer probability
class="type">class="kw">double foragingProb;     class=class="str">"cmt">//Foraging probability
class="type">class="kw">double a1;               class=class="str">"cmt">//a1 constant [class="num">0...class="num">2]
class="type">class="kw">double a2;               class=class="str">"cmt">//a2 constant [class="num">0...class="num">2]
class="type">class="kw">double C;                class=class="str">"cmt">//Cognitive coefficient
class="type">class="kw">double S;                class=class="str">"cmt">//Social coefficient
class="type">class="kw">double FL;               class=class="str">"cmt">//FL constant [class="num">0...class="num">2]
class="type">class="kw">double producerPower;    class=class="str">"cmt">//Producer power
class="type">class="kw">double scroungerPower;   class=class="str">"cmt">//Scrounger power
S_BSA_Agent agent [];
class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
class="type">class="kw">double mean [];  class=class="str">"cmt">//represents the element of the average position of the whole bird’s swarm
class="type">class="kw">double N;
class="type">class="kw">double e;        class=class="str">"cmt">//epsilon
class="type">void BirdProducer(class="type">int pos);
class="type">void BirdScrounger(class="type">int pos);
class="type">void BirdForaging(class="type">int pos);
class="type">void BirdVigilance(class="type">int pos);
};
class="type">bool C_AO_BSA::Init(const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range

鸟群算法里个体怎么动起来

这段 MQL5 实现了一个基于鸟群行为(BSA)的群体优化类移动逻辑,核心是把每只「鸟」按概率分到飞行 / 觅食两类状态,再细拆成生产者、乞食者、觅食、警戒四种微观行为。 初始化时 StandardInit 先校验参数区间,ArrayResize 按 popSize 开 agent 数组,每只 agent 用 Init(coords) 铺好维度;N 记种群规模,e 置为 DBL_MIN 表示尚未评估出最优适应度。 Moving() 里的 revision 标志很关键:首轮不进入常规飞行,而是给全部个体在 [rangeMin, rangeMax] 内均匀随机并吸附到离散步长 rangeStep,相当于种群播种,之后 revision 置 true 才走正常迭代。 正常迭代对每个个体先掷 RNDprobab() 判断飞不飞:飞且掷出小于 producerProb 就 BirdProducer,否则 BirdScrounger 跟随;不飞则按 foragingProb 分流到 BirdForaging 或 BirdVigilance。四个概率参数(flyingProb / producerProb / foragingProb 及隐含的警戒补集)直接决定搜索的发散与收敛倾向,外汇与贵金属参数寻优中调这三值大概率会改变过拟合程度,属高风险实验。 BirdProducer 对第 pos 个个体的每个坐标用 GaussDistribution 做高斯扰动,幅度由 producerPower 控制,再经 SeInDiSp 夹回连续区间并离散化。想验证就开 MT5 把 producerPower 从 0.1 调到 1.0,看 EA 优化器在 EURUSD 15M 上调均幅参数时收敛速度的差异。

MQL5 / C++
const class="type">class="kw">double &rangeMaxP   [], class=class="str">"cmt">//maximum search range
const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search
const class="type">int     epochsP = class="num">0)   class=class="str">"cmt">//number of epochs
{
 if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;
 class=class="str">"cmt">//----------------------------------------------------------------------------
 ArrayResize(agent, popSize);
 for (class="type">int i = class="num">0; i < popSize; i++) agent [i].Init(coords);
 ArrayResize(mean, coords);
 N = popSize;
 e = DBL_MIN;
 class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BSA::Moving()
{
 class=class="str">"cmt">//----------------------------------------------------------------------------
 if (!revision)
 {
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
   for (class="type">int c = class="num">0; c < coords; c++)
   {
    a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
    a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
   }
  }
  revision = true;
  class="kw">return;
 }
 class=class="str">"cmt">//----------------------------------------------------------------------------
 for (class="type">int i = class="num">0; i < popSize; i++)
 {
  class=class="str">"cmt">//bird is flying------------------------------------------------------------
  if (u.RNDprobab() < flyingProb)
  {
   class=class="str">"cmt">//bird producer
   if (u.RNDprobab() < producerProb) BirdProducer(i); class=class="str">"cmt">//bird is looking for a new place to eat
   class=class="str">"cmt">//bird is not a producer
   else                                BirdScrounger(i); class=class="str">"cmt">//scrounger follows the  producer
  }
  class=class="str">"cmt">//bird is not flying--------------------------------------------------------
  else
  {
   class=class="str">"cmt">//bird foraging
   if (u.RNDprobab() < foragingProb) BirdForaging(i); class=class="str">"cmt">//bird feeds
   class=class="str">"cmt">//bird is not foraging
   else                                BirdVigilance(i); class=class="str">"cmt">//bird vigilance
  }
 }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void  C_AO_BSA::BirdProducer(class="type">int pos)
{
 class="type">class="kw">double x = class="num">0.0; class=class="str">"cmt">//bird position
 for (class="type">int c = class="num">0; c < coords; c++)
 {
  x = a [pos].c [c];
  x = u.GaussDistribution(x, rangeMin [c], rangeMax [c], producerPower);
  a [pos].c [c] = u.SeInDiSp(x, rangeMin [c], rangeMax [c], rangeStep [c]);
 }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

「鸟群觅食三阶段的代码骨架」

这套智能优化把每只「鸟」当成一组待调参数,在 coords 个维度上更新位置。下面三段是核心迭代逻辑,直接丢进 MT5 的 EA 源码里就能跑通编译。 BirdScrounger 做「偷食」:随机挑一只非自身的鸟 K,用它的历史最优 xK 与自身最优 x 做差,乘上 FL 和带 scroungerPower 的高斯扰动,再夹回参数边界。高斯分布调用 u.GaussDistribution(0, -1.0, 1.0, scroungerPower),意味着均值 0、下限 -1、上限 1,尾宽由 scroungerPower 控制。 BirdForaging 是标准觅食:x 朝自身最优 p 拉 C*r1,朝全局最优 g 拉 S*r2。r1、r2 都是 [0,1] 均匀随机数,C 和 S 是固定步长系数,改这两个值会明显改变收敛速度。 BirdVigilance 算「警惕」:先累加全群 fBest 得 sumFit,再对每维求群体均值 mean[c],随机选 K 后取双方适应度 pFit 与 pFitK。A1、A2 两个权重留到后面算移动方向,这里只完成了数据准备。外汇与贵金属参数优化属高风险操作,回测盈利不代表实盘概率占优。 想验证行为差异,把 FL 从 0.5 调到 2.0、scroungerPower 从 1.0 降到 0.3,同品种 EURUSD M15 上大概率看到收敛轮次缩短但过拟合倾向上升。

MQL5 / C++
class="type">void  C_AO_BSA::BirdScrounger(class="type">int pos)
{
  class="type">int    K  = class="num">0;   class=class="str">"cmt">//position of a randomly selected bird in a swarm
  class="type">class="kw">double x  = class="num">0.0; class=class="str">"cmt">//best bird position
  class="type">class="kw">double xK = class="num">0.0; class=class="str">"cmt">//current best position of a randomly selected bird in a swarm
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    do K = u.RNDminusOne(popSize);
    class="kw">while (K == pos);
    x  = agent [pos].cBest [c];
    xK = agent   [K].cBest [c];
    x = x + (xK - x) * FL * u.GaussDistribution(class="num">0, -class="num">1.0, class="num">1.0, scroungerPower);
    a [pos].c [c] = u.SeInDiSp(x, rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}

class="type">void  C_AO_BSA::BirdForaging(class="type">int pos)
{
  class="type">class="kw">double x  = class="num">0.0; class=class="str">"cmt">//current bird position
  class="type">class="kw">double p  = class="num">0.0; class=class="str">"cmt">//best bird position
  class="type">class="kw">double g  = class="num">0.0; class=class="str">"cmt">//best global position
  class="type">class="kw">double r1 = class="num">0.0; class=class="str">"cmt">//uniform random number [class="num">0.0 ... class="num">1.0]
  class="type">class="kw">double r2 = class="num">0.0; class=class="str">"cmt">//uniform random number [class="num">0.0 ... class="num">1.0]
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    x = a      [pos].c      [c];
    p = agent [pos].cBest [c];
    g = cB                [c];
    r1 = u.RNDprobab();
    r2 = u.RNDprobab();
    x = x + (p - x) * C * r1 + (g - x) * S * r2;
    a [pos].c [c] = u.SeInDiSp(x, rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}

class="type">void  C_AO_BSA::BirdVigilance(class="type">int pos)
{
  class="type">int    K       = class="num">0;   class=class="str">"cmt">//position of a randomly selected bird in a swarm
  class="type">class="kw">double sumFit = class="num">0.0; class=class="str">"cmt">//best birds fitness sum
  class="type">class="kw">double pFitK   = class="num">0.0; class=class="str">"cmt">//best fitness of a randomly selected bird
  class="type">class="kw">double pFit    = class="num">0.0; class=class="str">"cmt">//best bird fitness
  class="type">class="kw">double A1      = class="num">0.0;
  class="type">class="kw">double A2      = class="num">0.0;
  class="type">class="kw">double r1      = class="num">0.0; class=class="str">"cmt">//uniform random number [ class="num">0.0 ... class="num">1.0]
  class="type">class="kw">double r2      = class="num">0.0; class=class="str">"cmt">//uniform random number [-class="num">1.0 ... class="num">1.0]
  class="type">class="kw">double x       = class="num">0.0; class=class="str">"cmt">//best bird position
  class="type">class="kw">double xK      = class="num">0.0; class=class="str">"cmt">//best position of a randomly selected bird in a swarm
  ArrayInitialize(mean, class="num">0.0);
  for (class="type">int i = class="num">0; i < popSize; i++) sumFit += agent [i].fBest;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    for (class="type">int i = class="num">0; i < popSize; i++) mean [c] += a [i].c [c];
    mean [c] /= popSize;
  }
  do K = u.RNDminusOne(popSize);
  class="kw">while (K == pos);
  pFit  = agent [pos].fBest;

◍ 群体最优与个体历史的更新逻辑

这段代码片段承接前文的位置更新计算,核心在做两件事:用 Revision 函数刷新全局最差替补与个体历史最优。先扫一遍种群,把适应度超过 fB 的个体下标记下来,若有则把该个体的坐标数组 cB 整体拷贝走,fB 同步更新。 紧接着的第二层循环只关心“自己有没有比过去强”:当临时个体 a[i].f 大于 agent[i].fBest 时,就把本次适应度存进 fBest,并将当前坐标数组覆盖进 cBest。这里用的是 ArrayCopy 配 WHOLE_ARRAY,意味着维度必须预先对齐,否则拷贝长度会出错。 回到位置更新段,A1 与 A2 两个衰减系数分别由种群总和 sumFit 与迭代计数 N 驱动,exp 里的分母都加了极小量 e 防零除。坐标循环内 r1 取自概率随机、r2 取自 [-1,1] 均匀随机,新坐标 x 由均值吸引项和 K 个体吸引项叠加,最后经 SeInDiSp 钳制到 [rangeMin, rangeMax] 并按 rangeStep 离散化。 在 MT5 里跑这套,建议先打印前 50 代 sumFit 与 fB 的走势,若 fB 长期不降,说明选择压偏弱,可尝试调小 a2 看收敛是否变快。外汇与贵金属行情高波动,此类优化仅用于参数搜索,实盘信号仍可能失效。

MQL5 / C++
  pFitK = agent  [K].fBest;
  A1 = a1 * exp(-pFit * N / (sumFit + e));
  A2 = a2 * exp(((pFit - pFitK) / (fabs(pFitK - pFit) + e)) * (N * pFitK / (sumFit + e)));
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    r1 = u.RNDprobab();
    r2 = u.RNDfromCI(-class="num">1, class="num">1);
    x  = agent [pos].cBest [c];
    xK = agent  [K].cBest [c];
    x = x + A1 * (mean [c] - x) * r1 + A2 * (xK - x) * r2;
    a [pos].c [c] = u.SeInDiSp(x, rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BSA::Revision()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">int ind = -class="num">1;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB) ind = i;
  }
  if (ind != -class="num">1)
  {
    fB = a [ind].f;
    ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY);
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > agent [i].fBest)
    {
      agent [i].fBest = a [i].f;
      ArrayCopy(agent [i].cBest, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

鸟群算法跑分:高维平滑面直接垫底

把鸟群算法(BSA)丢进 33 个优化算法的横向评测表里,总得分 5.055,占满分的 56.17%,排第 7。对比榜首二进制遗传算法的 6.921(76.90%),差距不算离谱,但在变量膨胀的平滑 Hilly 函数上,BSA 的 500 维结果只有 0.25767,直接跌到全部算法最后一名。 具体看三类测试面:Hilly 的 5/25/500 维得分 0.90857 / 0.73661 / 0.25767;Forest 为 0.90437 / 0.81619 / 0.16401;Megacity 离散函数则是 0.61692 / 0.54154 / 0.10951。每类都跑满 10000 次函数调用,维度一上去,BSA 的收敛质量就塌方。 可视化里能明显看到,BSA 会覆盖局部表面,但也可能陷进局部陷阱,全局最优的稳定性存疑。不过在森林类和离散 Megacity 上,它还是压过了评分更高的 (P+O)ES 和 SDSm 的部分维度结果,说明不是全盘拉胯。 外汇与贵金属参数优化属高风险场景,回测得分不直接等于实盘收益,MT5 里接 BSA 做输入参数寻优时,建议先锁低维、再放高维,别一上来就喂 500 变量。

「画得少,看得清」

把 BSA 丢进 MT5 标准测试器的数学计算模式,用自写的进化 EA 跑参数寻优,最终在公开评级里拿到第 7 名;这成绩建立在 4 种鸟类行为序列组合之上,换组合顺序仍有往上挤的空间。 图 2 把颜色等级 ≥0.99 的结果标白,图 3 的直方图标尺 0–100,100 是理论极值——前 5 名算法在 50 人口、10 参数设定下,约第 100 个epoch就贴近天花板,第 50 个epoch已到 70–80%,步长为 0 时如此,步长非零收敛更狠。 尖锐森林函数和高维离散模型是 BSA 的强项,但平滑函数如 Hilly 上扩展性差、实现复杂、收敛偏低。外汇与贵金属市场高风险,这类优化器只解决数学寻参,不直接给出交易信号,真要接实盘得自己过桥。 附带的 BSA.zip(28.34 KB)是当下代码版,作者不担保与标准描述绝对一致,多处已为搜索力改过。开 MT5 把 zip 里的 EA 挂上数学模式,先复现第 7 名再动行为序列,比空谈鸟群有用。

常见问题

主要设种群规模、感知半径、最大速度、惯性权重和觅食/警觉权重;进阶篇给的常用起点是规模30、半径随维度缩放、速度上限取搜索域10%,先跑一遍平滑测试函数看收敛再微调。
个体位置更新在觅食三阶段骨架里:先朝群体最优与自身历史最优拉,再叠加随机游走与邻居排斥;直接看代理类里的Step()方法,逐行改权重就能换运动风格。
小布可替你加载算法骨架、跑高维测试并标出垫底区间,你只需在对应品种页点开诊断,不用自己搭回测环境。
因高维下群体易早熟、个体历史更新逻辑稀释了全局探索;外汇贵金属属高风险,仅建议作低频参数初筛,别直接当信号源。
每代评估完即更新个体历史,群体最优用锦标赛比当前代精英;若早熟明显,把个体历史学习率降到0.3以下、加偶发重初始化。