种群优化算法:Boids(虚拟生物)算法·进阶篇
📘

种群优化算法:Boids(虚拟生物)算法·进阶篇

第 2/2 篇

Boids 寻优类的成员与初始化拆解

这段声明定义了一个基于鸟群行为(Boids)的优化代理类,把聚拢、分离、对齐三类作用力权重和距离阈值都暴露为可调双精度字段,交易者改这几个数就能改变种群在参数空间里的搜索形态。 类里私有部分藏了 Distance、LimitSpeed、KeepWithinBounds 等函数,说明速度上限按各维度 rangeMax-rangeMin 动态算,distanceMax 则是各维速度上限平方和,直接决定邻居判定的空间尺度。 Init 函数先做 StandardInit 校验边界数组,再把 agent 数组按 popSize 扩容,每个代理调用自身 Init(coords) 完成坐标维度初始化;下面这段就是核心起步代码。 速度上限数组 speedMax 按维度赋值后,distanceMax 累加 pow(speedMax[c],2),这意味着若某维搜索区间跨度 100、另一维跨度 50,distanceMax 会落到 10000+2500=12500,邻居半径平方据此定标。外汇与贵金属杠杆品种用此类寻参时,区间设宽可能导致过拟合,回测结果倾向失真,属高风险操作。

MQL5 / C++
class="type">bool C_AO_Boids::Init(class="kw">const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range
                        class="kw">const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">//maximum search range
                        class="kw">const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search
                        class="kw">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);
  distanceMax = class="num">0;
  ArrayResize(speedMax, coords);
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    speedMax [c] = rangeMax [c] - rangeMin [c];
    distanceMax += pow(speedMax [c], class="num">2);
  }

「用全局变量把群体参数喂给运动函数」

这段逻辑解决的是 Boids 群体算法里「参数从哪来、何时重置」的问题。初始化收尾时,先把 distanceMax 开平方当作有效半径,再把 8 个权重与距离参数通过 GlobalVariableSet 写进全局变量,键名从 1cohesionWeight 到 8minSpeed 顺序排列,最后置 #reset 为 1.0 并 return true。 Moving() 每次被调都会先读 #reset:若为 1.0,则清空 revision 标记并把 #reset 改回 0.0,随后从全局变量把 8 个参数逐个读回本地成员。这样 EA 重启或外部面板改参后,群体能立刻吃到新值,而不用重编译。 当 revision 为 false 时,代码用两层 for 给 popSize 个 agent 在 coords 维上随机撒点:位置取 rangeMin~rangeMax 均匀随机,速度取区间跨度乘 [-1,1] 随机再乘 0.001 的微小系数,同时算好离散网格索引。初始化完置 revision=true 并直接 return,下一帧才进入正式迭代。 正式帧里对每个 agent 依次跑 CalculateMass、Cohesion、Separation、Alignment,再用 LimitSpeed 限住速度、KeepWithinBounds 防越界,最后位置按 dx 累加并更新网格索引。外汇与贵金属市场波动剧烈,这类模拟仅作形态参考,实盘使用群体信号须自担高风险。

MQL5 / C++
  distanceMax = sqrt(distanceMax);
  GlobalVariableSet("class="macro">#reset", class="num">1.0);
  GlobalVariableSet("1cohesionWeight",   params [class="num">1].val);
  GlobalVariableSet("2cohesionDist",     params [class="num">2].val);
  GlobalVariableSet("3separationWeight", params [class="num">3].val);
  GlobalVariableSet("4separationDist",   params [class="num">4].val);
  GlobalVariableSet("5alignmentWeight",  params [class="num">5].val);
  GlobalVariableSet("6alignmentDist",    params [class="num">6].val);
  GlobalVariableSet("7maxSpeed",         params [class="num">7].val);
  GlobalVariableSet("8minSpeed",         params [class="num">8].val);
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_Boids::Moving()
{
  class="type">class="kw">double reset = GlobalVariableGet("class="macro">#reset");
  if (reset == class="num">1.0)
  {
    revision = class="kw">false;
    GlobalVariableSet("class="macro">#reset", class="num">0.0);
  }
  cohesionWeight   = GlobalVariableGet("1cohesionWeight");
  cohesionDist     = GlobalVariableGet("2cohesionDist");
  separationWeight = GlobalVariableGet("3separationWeight");
  separationDist   = GlobalVariableGet("4separationDist");
  alignmentWeight  = GlobalVariableGet("5alignmentWeight");
  alignmentDist    = GlobalVariableGet("6alignmentDist");
  maxSpeed         = GlobalVariableGet("7maxSpeed");
  minSpeed         = GlobalVariableGet("8minSpeed");
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        agent [i].x [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
        agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI(-class="num">1.0, class="num">1.0) * class="num">0.001;
        a [i].c [c] = u.SeInDiSp(agent [i].x [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++)
  {
    CalculateMass();
    Cohesion(agent [i], i);
    Separation(agent [i], i);
    Alignment(agent [i], i);
    LimitSpeed(agent [i]);
    KeepWithinBounds(agent [i]);
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      agent [i].x [c] += agent [i].dx [c];
      a [i].c [c] = u.SeInDiSp(agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}

◍ 群体智能里的三类作用力拆解

把 Boids 模型搬进 MT5 做行情聚类时,核心就是给每个 agent 算三种局部力:聚合、分离、对齐。下面这段实现里,CalculateMass 先扫一遍种群,用 DBL_MAX 初始化找出适应度极值,再把每个 agent 的 f 线性映射到 0~1 区间作为质量 m,质量越大在后续加权里话语权越高。 聚合力 Cohesion 只拉拢半径 distanceMax * cohesionDist 内的邻居,把它们的坐标累加求平均中心,再让当前 boid 朝中心偏移,权重由 cohesionWeight 控制。分离力 Separation 则在 distanceMax * separationDist 内做互斥,直接把「自己减邻居」的坐标差乘 separationWeight 加进 dx,避免 agent 叠在一起。 对齐 Alignment 取邻域内其他 agent 的速度向量 dx 均值,让当前 boid 向群体平均速度靠拢。三种力的半径都挂在 distanceMax 上,调参时改一个基准距离就能整体缩放邻域灵敏度;外汇与贵金属波动跳变快,这类模型在高杠杆下误触发可能放大滑点,实盘前务必在策略测试器里用历史数据验证邻域参数。 代码里 numNeighbors 为 0 时聚合与对齐会触发除零,MT5 跑出来是 INF 而非报错,复制去用时记得加一句保护。

MQL5 / C++
class="type">void C_AO_Boids::CalculateMass()
{
  class="type">class="kw">double maxMass = -DBL_MAX;
  class="type">class="kw">double minMass =  DBL_MAX;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > maxMass) maxMass = a [i].f;
    if (a [i].f < minMass) minMass = a [i].f;
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    agent [i].m = u.Scale(a [i].f, minMass, maxMass, class="num">0.0, class="num">1.0);
  }
}

class="type">void C_AO_Boids::Cohesion(S_Boids_Agent &boid, class="type">int pos)
{
  class="type">class="kw">double centerX [];
  ArrayResize(centerX, coords);
  ArrayInitialize(centerX, class="num">0.0);
  class="type">int   numNeighbors = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (pos != i)
    {
      if (Distance(boid, agent [i]) < distanceMax * cohesionDist)
      {
        for (class="type">int c = class="num">0; c < coords; c++)
        {
          centerX [c] += agent [i].x [c];
        }
        numNeighbors++;
      }
    }
  }
 for (class="type">int c = class="num">0; c < coords; c++)
  {
    centerX [c] /= numNeighbors;
    boid.dx [c] += (centerX [c] - boid.x [c]) * cohesionWeight;
  }
}

class="type">void C_AO_Boids::Separation(S_Boids_Agent &boid, class="type">int pos)
{
  class="type">class="kw">double moveX [];
  ArrayResize(moveX, coords);
  ArrayInitialize(moveX, class="num">0.0);
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (pos != i)
    {
      if (Distance(boid, agent [i]) < distanceMax * separationDist)
      {
        for (class="type">int c = class="num">0; c < coords; c++)
        {
          moveX [c] += boid.x [c] - agent [i].x [c];
        }
      }
    }
  }
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    boid.dx [c] += moveX [c] * separationWeight;
  }
}

class="type">void C_AO_Boids::Alignment(S_Boids_Agent &boid, class="type">int pos)
{
  class="type">class="kw">double avgDX [];
  ArrayResize(avgDX, coords);
  ArrayInitialize(avgDX, class="num">0.0);
  class="type">int numNeighbors = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (pos != i)
    {
      if (Distance(boid, agent [i]) < distanceMax * alignmentDist)
      {
        for (class="type">int c = class="num">0; c < coords; c++)
        {
          avgDX [c] += agent [i].dx [c];

对齐、限速与边界反弹的实现细节

这段群体算法里,对齐阶段先把邻域个体的平均速度 avgDX 按邻居数归一,再拿 alignmentWeight 做加权修正:boid.dx[c] += (avgDX[c] - boid.dx[c]) * alignmentWeight,权重调大,群体方向收敛更快,但容易在震荡行情里集体追错边。 LimitSpeed 先求当前速度模长 speed = sqrt(∑dx²),随后用 (rangeMax[c]-rangeMin[c])*minSpeed 算出最小速度阈值 d。若某维速度绝对值低于 d,就钳到 ±d,避免 agent 在价格区间内近乎静止;上限则乘 speedMax[c]*maxSpeed,外汇与贵金属杠杆高,这种硬限速能压住单维跳变带来的虚假信号。 KeepWithinBounds 已弃用软边界注释块,改成越界即反向:boid.dx[c] *= -1,turnFactor 留作 (rangeMax[c]-rangeMin[c])*0.0001 的潜在回调系数。注意 Distance 函数里有笔误——pow(boid1.x[c]-boid1.x[c],2) 永远为 0,应改为 boid1.x[c]-boid2.x[c],否则邻域距离全算成 0,对齐逻辑会失效。 Revision 每代扫一遍种群,抓到优于历史最优 fB 的个体就 ArrayCopy 进 cB;ind 初始 -1 作未更新标记,MT5 里直接跑能验证最优解是否被及时记录。

MQL5 / C++
   }
      numNeighbors++;
      }
    }
  }
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      avgDX  [c] /= numNeighbors;
      boid.dx [c] += (avgDX [c] - boid.dx [c]) * alignmentWeight;
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_Boids::LimitSpeed(S_Boids_Agent &boid)
{
  class="type">class="kw">double speed = class="num">0;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    speed += boid.dx [c] * boid.dx [c];
  }
  speed = sqrt(speed);
  class="type">class="kw">double d = class="num">0;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    d = (rangeMax [c] - rangeMin [c]) * minSpeed;
    boid.dx [c] = (boid.dx [c] / speed) * speedMax [c] * maxSpeed;
    if (fabs(boid.dx [c]) < d)
    {
      if (boid.dx [c] < class="num">0.0) boid.dx [c] = -d;
      else                    boid.dx [c] =  d;
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_Boids::KeepWithinBounds(S_Boids_Agent &boid)
{
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    class="type">class="kw">double margin    = class="num">0; class=class="str">"cmt">//(rangeMax [c] - rangeMin [c])* class="num">0.00001;
    class="type">class="kw">double turnFactor = (rangeMax [c] - rangeMin [c]) * class="num">0.0001;
    class=class="str">"cmt">/*
    if (boid.x [c] < rangeMin [c] + margin)
    {
      boid.dx [c] += turnFactor;
    }
    if (boid.x [c] > rangeMax [c] - margin)
    {
      boid.dx [c] -= turnFactor;
    }
    */
    if (boid.x [c] < rangeMin [c])
    {
      class=class="str">"cmt">//boid.x [c] = rangeMax [c];
      boid.dx [c] *= -class="num">1;
      
    }
    if (boid.x [c] > rangeMax [c])
    {
      class=class="str">"cmt">//boid.x [c] = rangeMin [c];
      boid.dx [c] *= -class="num">1;
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_Boids::Distance(S_Boids_Agent &boid1, S_Boids_Agent &boid2)
{
  class="type">class="kw">double dist = class="num">0;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    dist += pow(boid1.x [c] - boid1.x [c], class="num">2);
  }
  class="kw">return sqrt(dist);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_Boids::Revision()
{
  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">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

「Boids跑分实测:效率仅到天花板四分之一」

把Boids塞进Hilly、Forest、Megacity三组标准测试函数里各跑10万次,得分随问题规模放大反而塌得更快:5个Hilly得0.433,25个掉到0.306,500个只剩0.254;Megacity从0.278一路滑到0.098。三项加总2.22889,刚好是最大可能分9的24.77%,说明这套仿鸟群逻辑在通用优化里上限偏低。 横向拉一张33种算法的排名表,Boids排第28,总分2.229,紧挨着它的集群智能兄弟PSO(2.230,也是24.77%)。前排的BGA拿6.921、DE拿4.955,差距不是调参能抹平的。 在MT5里如果你打算用Boids类思路做参数寻优,建议先拿小样本函数验证——外汇与贵金属市场波动结构更复杂,直接套这套低分算法,过拟合和失效的概率倾向偏高。

◍ 把这条线请下神坛

回测里 Boids 在 1000 变量的 Forest 与 Megacity 函数上评级逼近 0.99,和白表上半区算法不相上下,说明高维空间里它未必一无是处。 但 Hilly 这类平滑函数上它扩展性差,收敛低、计算重,别在 MT5 优化器里把它当万能钥匙。 外汇与贵金属参数寻优属高风险操作,算法结果仅作概率参考;真要试,先拿 ZIP 里的 28.61 KB 源码在小样本跑通再放大。

常见问题

核心要初始化位置、速度、感知半径与最大速度,感知半径建议先取品种日均波幅的1~2倍,避免群体过早抱团或散开。
把感知半径、分离/对齐/聚合权重声明为全局变量,运动函数直接读取,调参时只改全局处即可,不用动函数体。
小布盯盘的AIGC可内置群体智能诊断,打开对应品种页即可看群体行为偏离与疑似拐点提示,不用自己写循环。
对齐取邻域平均速度归一化,限速对合速度做截断,单步位移不超过最大速度,边界用反弹而非穿越,贵金属等高波动品种需调小限速值。
概率上仅适合做辅助过滤而非主信号,建议降权使用,把它从圣杯神坛请下,当噪声过滤器更稳妥。