基于人工生态系统的优化(AEO)算法·进阶篇
🌿

基于人工生态系统的优化(AEO)算法·进阶篇

(2/3)·从草到食腐动物的能量链,怎样变成数学上的更新规则

偏理论 第 2/3 篇
把优化算法当黑箱调参的人,常忽略底层机制对收敛行为的影响。AEO用生态系统隐喻包装,若只看参数面板,很容易误判它为何在某类函数上停滞。理解智能体间的能量传递,才能解释那些看似随机的跳跃。

「种群初始化与消费模型的代码骨架」

这套 AO/AEO 优化器的 Moving() 方法,第一步靠 revision 标志位做种群初始化:未修订时,对 popSize 个个体、coords 个维度,先用 RNDfromCI 在 [rangeMin, rangeMax] 内随机撒点,再经 SeInDiSp 按 rangeStep 吸附到离散网格,之后置 revision=true 并直接返回。 若 consModel==0 走消费模型分支:i==0 的领航个体用 GaussDistribution 以标准差 8 在边界内做高斯扰动后吸附网格;i==popSize-1 调用 GrassBehavior 当草食边界;倒数第二及之前按概率分流——i>=popSize-2 时 r<0.333 走 HerbivoreBehavior,r<0.667 走 CarnivoreBehavior,否则 OmnivoreBehavior;更早个体则 r<0.5 Carnivore 否则 Omnivore。 代码里的硬编码概率(0.333 / 0.667 / 0.5)和标准差 8,都是可直接在 MT5 里改的参数。外汇与贵金属市场波动剧烈、杠杆风险高,这类群体智能寻优仅作策略参数探索,实盘前务必用历史数据回测验证效果。

MQL5 / C++
class="type">void C_AO_AEO::Moving()
{
 epochNow++;
 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">// Consumption ---------------------------------------------------------------
 if (consModel == class="num">0)
 {
  class="type">class="kw">double r = class="num">0.0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
   if (i == class="num">0)
   {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
     a [i].c [c] = u.GaussDistribution(a [i].c [c], rangeMin [c], rangeMax [c], class="num">8);
     a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
    class="kw">continue;
   }
   if (i == popSize - class="num">1) GrassBehavior(a [i]);
   else
   {
    if (i >= popSize - class="num">2)
    {
     for (class="type">int c = class="num">0; c < coords; c++)
     {
      r = u.RNDprobab();
      if (r < class="num">0.333) HerbivoreBehavior(a [i], c);
      else
      {
       if (r < class="num">0.667)
       {
        CarnivoreBehavior(a [i], i, c);
       }
       else
       {
        OmnivoreBehavior(a [i], i, c);
       }
      }
     }
    }
    else
    {
     for (class="type">int c = class="num">0; c < coords; c++)
     {
      r = u.RNDprobab();
      if (r < class="num">0.5) CarnivoreBehavior(a [i], i, c);
      else       OmnivoreBehavior(a [i], i, c);
     }
    }
   }
  }
  consModel++;
  class="kw">return;
 }
 class=class="str">"cmt">// Decomposition -------------------------------------------------------------

MQL5 / C++
class="type">void C_AO_AEO::Moving()
{
 epochNow++;
 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">// Consumption ---------------------------------------------------------------
 if (consModel == class="num">0)
 {
  class="type">class="kw">double r = class="num">0.0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
   if (i == class="num">0)
   {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
     a [i].c [c] = u.GaussDistribution(a [i].c [c], rangeMin [c], rangeMax [c], class="num">8);
     a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
    class="kw">continue;
   }
   if (i == popSize - class="num">1) GrassBehavior(a [i]);
   else
   {
    if (i >= popSize - class="num">2)
    {
     for (class="type">int c = class="num">0; c < coords; c++)
     {
      r = u.RNDprobab();
      if (r < class="num">0.333) HerbivoreBehavior(a [i], c);
      else
      {
       if (r < class="num">0.667)
       {
        CarnivoreBehavior(a [i], i, c);
       }
       else
       {
        OmnivoreBehavior(a [i], i, c);
       }
      }
     }
    }
    else
    {
     for (class="type">int c = class="num">0; c < coords; c++)
     {
      r = u.RNDprobab();
      if (r < class="num">0.5) CarnivoreBehavior(a [i], i, c);
      else       OmnivoreBehavior(a [i], i, c);
     }
    }
   }
  }
  consModel++;
  class="kw">return;
 }
 class=class="str">"cmt">// Decomposition -------------------------------------------------------------

◍ 差分变异与草食/食草行为的坐标更新

这段逻辑实现了种群差分变异与两类生物启发式行为的代理坐标刷新。先看变异段:对每个 agent 的每个维度 c,先随机选一个下标 j,用步长 D = 3.0 * (cB[c] - a[j].c[c]) 向当前最优 cB 做差分扰动,边界越界时改用区间随机值回填,最后经高斯采样与离散化落入合法栅格。 Revision() 负责精英保留:遍历种群把最优适应度写进 fB 与 cB,同时更新每个 agent 的历史最优 cB,并按 fB 降序重排。注意这里用的是大于号筛选,说明适应度越大越优。 GrassBehavior 套用草地模型 Xn + α*(Xn - Xr),其中 α 随迭代线性衰减:α = (1 - epochNow/epochs)。在 MT5 里把 epochs 设小(如 50)会看到早期扰动剧烈、后期趋稳的现象。 HerbivoreBehavior 让普通 agent 朝最差个体反向逃逸:Xi = Xi + C*(Xi - X1),C 来自莱维飞行分布。外汇与贵金属参数寻优中,levisPower 调大可能让跳出局部的概率升高,但回测过拟合风险也同步放大,属高风险操作。

MQL5 / C++
  class="type">int    j   = class="num">0;
  class="type">class="kw">double D   = class="num">0.0;
  class="type">class="kw">double min = class="num">0.0;
  class="type">class="kw">double max = class="num">0.0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      j = u.RNDminusOne(popSize);
      D = class="num">3.0 * (cB [c] - a [j].c [c]); class=class="str">"cmt">// * u.RNDprobab();
      min = cB [c] - D;
      max = cB [c] + D;
      if (min < rangeMin [c]) min = u.RNDfromCI(rangeMin [c], cB [c]);
      if (max > rangeMax [c]) min = u.RNDfromCI(cB [c], rangeMax [c]);
      a [i].c [c] = u.GaussDistribution(cB [c], min, max, class="num">1);
      a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
  consModel = class="num">0;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_AEO::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)
    {
      fB = a [i].f;
      ind = i;
    }
  }
  if (ind != -class="num">1) 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 > a [i].fB)
    {
      a [i].fB = a [i].f;
      ArrayCopy(a [i].cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
  u.Sorting_fB(a, aT, popSize);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Grass
class=class="str">"cmt">// (Worst) X1 X2 X3 ...... Xn(Best)
class=class="str">"cmt">// X1(t+class="num">1) = (class="num">1 - α) * Xn(t) + α * Xrnd(t)
class=class="str">"cmt">// α = (class="num">1 - t / T) * rnd [class="num">0.0; class="num">1.0]
class=class="str">"cmt">// Xrnd = rnd [Xmin; Xmax]
class="type">void C_AO_AEO::GrassBehavior(S_AO_Agent &animal)
{
  class=class="str">"cmt">//class="num">0) (class="num">1 - α) * Xn + α * Xrnd
  class=class="str">"cmt">//class="num">1) Xn - α * Xn + α * Xrnd
  class=class="str">"cmt">//class="num">2) Xn + α * (Xrnd - Xn)
  class="type">class="kw">double α = (class="num">1.0 - (class="type">class="kw">double)epochNow / epochs);
  class="type">class="kw">double X1 = class="num">0.0;
  class="type">class="kw">double Xn = class="num">0.0;
  class="type">class="kw">double Xr = class="num">0.0;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    Xr = u.RNDfromCI(rangeMin [c], rangeMax [c]);
    Xn = cB [c];
    class=class="str">"cmt">//X1 = Xn + α * (Xr - Xn);
    X1 = Xn + α * (Xn - Xr);
    animal.c [c] = u.SeInDiSp(X1, rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Herbivore
class=class="str">"cmt">// (Worst) X1 X2 X3 ...... Xn(Best)
class=class="str">"cmt">// Xi(t+class="num">1) = Xi(t) + C * (Xi(t) - X1(t)) for i ∈ [class="num">2, n]
class=class="str">"cmt">// Herbivore eats only the one with the highest energy(eats the one with the worst FF)
class="type">void C_AO_AEO::HerbivoreBehavior(S_AO_Agent &animal, class="type">int indCoord)
{
  class="type">class="kw">double Xi = animal.c [indCoord];
  class="type">class="kw">double X1 = a [popSize - class="num">1].c [indCoord];
  class="type">class="kw">double C  = u.LevyFlightDistribution(levisPower);
  Xi = Xi + C * (Xi - X1);

捕食者行为里的坐标更新逻辑

在 AO/AEO 智能体优化器里,肉食与杂食行为直接改写入 agent 的连续坐标值,再经 SeInDiSp 约束回参数区间。注意 CarnivoreBehavior 中实际执行的是 Xi = Xi + C * (Xj - Xi),与注释里的 Xi + C*(Xi - Xj) 相反,意味着向随机更差个体 j 做莱维步长靠近,而非逃离。 OmnivoreBehavior 用 r 在「向最优 X1 偏移」与「向随机较差 Xj 偏移」之间插值:Xi = Xi + C*r*(Xi - X1) + (1-r)*(Xi - Xj)。其中 j 被限制在 indAnimal+1 到 popSize-1,即只吃排名更靠后的个体,符合「吃能量更高(FF 更差)者」的设定。 Moving() 首轮若 revision 为 false,会用 RNDfromCI 在每维 rangeMin~rangeMax 均匀撒点并离散化,之后置 revision=true 并返回;后续轮次引入 α = 1 - epochNow/epochs 控制生产算子权重,α 随迭代从近 1 线性衰减到 0,老种群信息占比逐步上升。 开 MT5 把这几段塞进你的 EA 源码,把 levisPower 从 1.5 调到 1.8 跑一遍,莱维尾重变化可能让 Xj 方向步长更极端,外汇参数寻优的高风险在于过拟合历史样本。

MQL5 / C++
  animal.c [indCoord] = u.SeInDiSp(Xi, rangeMin [indCoord], rangeMax [indCoord], rangeStep [indCoord]);
}
class=class="str">"cmt">// Carnivorous
class=class="str">"cmt">// (Worst) X1 X2 X3 ...... Xn(Best)
class=class="str">"cmt">// Xi(t+class="num">1) = Xi(t) + C * (Xi(t) - Xj(t)) for i ∈ [class="num">3, n]
class=class="str">"cmt">// Carnivore eats those with less energy(eats those with higher FF)
class="type">void C_AO_AEO::CarnivoreBehavior(S_AO_Agent &animal, class="type">int indAnimal, class="type">int indCoord)
{
  class=class="str">"cmt">//class="type">class="kw">double Xi = animal.c [indCoord];
  class="type">class="kw">double Xi = animal.cB [indCoord];
  class="type">int    j  = u.RNDminusOne(indAnimal);
  class=class="str">"cmt">//class="type">class="kw">double Xj = a [j].c [indCoord];
  class="type">class="kw">double Xj = a [j].cB [indCoord];
  class="type">class="kw">double C  = u.LevyFlightDistribution(levisPower);
  class=class="str">"cmt">//Xi = Xi + C * (Xi - Xj);
  Xi = Xi + C * (Xj - Xi);
  animal.c [indCoord] = u.SeInDiSp(Xi, rangeMin [indCoord], rangeMax [indCoord], rangeStep [indCoord]);
}
class=class="str">"cmt">// Omnivorous
class=class="str">"cmt">// (Worst) X1 X2 X3 ...... Xn(Best)
class=class="str">"cmt">// Xi(t+class="num">1) = Xi(t) + C * r2 * (Xi(t) - X1(t)) + (class="num">1 - r2) * (Xi(t) - Xj(t)) for i ∈ [class="num">3, n]
class=class="str">"cmt">// An omnivore eats everyone who has more energy(grass and small animals, that is, those who have worse FF)
class="type">void C_AO_AEO::OmnivoreBehavior(S_AO_Agent &animal, class="type">int indAnimal, class="type">int indCoord)
{
  class=class="str">"cmt">//class="type">class="kw">double Xi = animal.c [indCoord];
  class="type">class="kw">double Xi = animal.cB [indCoord];
  class=class="str">"cmt">//class="type">class="kw">double X1 = a [popSize - class="num">1].c [indCoord];
  class="type">class="kw">double X1 = a [popSize - class="num">1].cB [indCoord];
  class="type">int    j  = u.RNDintInRange(indAnimal + class="num">1, popSize - class="num">1);
  class=class="str">"cmt">//class="type">class="kw">double Xj = a [j].c [indCoord];
  class="type">class="kw">double Xj = a [j].cB [indCoord];
  class="type">class="kw">double C  = u.LevyFlightDistribution(levisPower);
  class="type">class="kw">double r  = u.RNDprobab();
  Xi = Xi + C * r * (Xi - X1) + (class="num">1.0 - r) * (Xi - Xj);
  animal.c [indCoord] = u.SeInDiSp(Xi, rangeMin [indCoord], rangeMax [indCoord], rangeStep [indCoord]);
}
class="type">void C_AO_AEO::Moving()
{
  epochNow++;
  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="type">class="kw">double α = (class="num">1.0 - (class="type">class="kw">double)epochNow / epochs);
  class="type">class="kw">double Xi  = class="num">0.0;
  class="type">class="kw">double Xb  = class="num">0.0;
  class="type">class="kw">double Xr  = class="num">0.0;
  class="type">class="kw">double Xj  = class="num">0.0;
  class="type">class="kw">double C   = class="num">0.0;
  class="type">int    j   = class="num">0;
  class="type">class="kw">double r   = class="num">0.0;
}

「捕食者模型的两种消耗阶段」

群体智能优化里,消耗阶段(Consumption)分两个子模型切换执行。consModel 为 0 时先做初始化扩散:每个个体各维度以当前最优 cB[c] 为基,加上 α 乘反向随机区间偏移,再夹回参数边界与步长。 consModel 自增后下次进入 1,才是真正的食性行为。此时跳过前两个个体(i>1),按随机数 r 切三路:小于 0.333 走食草,用莱维飞行 C 向最优反向拉;0.333~0.667 走食肉,向随机另一体 j 反向拉;其余走杂食,混合最优与随机体。外汇与贵金属参数寻优用这套,杠杆风险高,回测过拟合概率偏大。 下面这段是 consModel==0 与 ==1 前半的核心代码,可直接贴 MT5 看个体坐标怎么被改写。

MQL5 / C++
  if (consModel == class="num">0)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        Xb = cB [c];
        Xr = u.RNDfromCI(rangeMin [c], rangeMax [c]);
        class=class="str">"cmt">//Xi = Xb + α * (Xr - Xb);
        Xi = Xb + α * (Xb - Xr);
        a [i].c [c] = u.SeInDiSp(Xi, rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    consModel++;
    class="kw">return;
  }
  class=class="str">"cmt">// Consumption --------------------------------------------------------------- 
  if (consModel == class="num">1)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      if (i > class="num">1)
      {
        for (class="type">int c = class="num">0; c < coords; c++)
        {
          r = u.RNDprobab();
          class=class="str">"cmt">// Herbivore behavior ------------------------------------------------ 
          class=class="str">"cmt">//Xi(t + class="num">1) = Xi(t) + C * (Xi(t) - Xb(t));
          if (r < class="num">0.333)
          {
            C  = u.LevyFlightDistribution(levisPower);
            Xb = cB [c];
            class=class="str">"cmt">//Xi = a [i].c [c];
            Xi = a [i].cB [c];
            class=class="str">"cmt">//Xi = Xi + C * (Xi - Xb);
            Xi = Xi + C * (Xb - Xi);
          }
          else
          {
            class=class="str">"cmt">// Carnivore behavior ---------------------------------------------- 
            class=class="str">"cmt">//Xi(t + class="num">1) = Xi(t) + C * (Xi(t) - Xj(t));
            if (r < class="num">0.667)
            {
              C  = u.LevyFlightDistribution(levisPower);
              j  = u.RNDminusOne(i);
              class=class="str">"cmt">//Xj = a [j].c [c];
              Xj = a [j].cB [c];
              class=class="str">"cmt">//Xi = a [i].c [c];
              Xi = a [i].cB [c];
              class=class="str">"cmt">//Xi = Xi + C * (Xi - Xj);
              Xi = Xi + C * (Xj - Xi);
            }
            class=class="str">"cmt">// Omnivore behavior ----------------------------------------------- 
            class=class="str">"cmt">//Xi(t + class="num">1) = Xi(t) + C * r2 * (Xi(t) - Xb(t)) +
            class=class="str">"cmt">//             (class="num">1 - r2) * (Xi(t) - Xj(t));
            else
            {
              C  = u.LevyFlightDistribution(levisPower);
              Xb = cB [c];
              j  = u.RNDminusOne(i);

◍ 分解阶段的莱维飞行扰动

这段是种群优化里「分解」阶段的实现,核心是用莱维飞行给当前最优位置加长尾随机步,避免陷在局部极值。D 取 0~3 的均匀随机数,h 以 50% 概率取 ±1,C 来自莱维分布,三者共同决定扰动幅度。 内层循环对每个坐标维度做更新:x = a[i].cB[c] + D*(C*a[i].cB[c] - h*a[j].c[c]),再用 SeInDiSp 把结果夹回 [rangeMin, rangeMax] 并按 rangeStep 离散化。注意 a[j].c[c] 的 j 由 RNDminusOne 生成,保证不等于 i。 莱维飞行的幂参数 levisPower 直接控制步长肥尾程度;在 MT5 里把 levisPower 从 1.5 调到 1.8,可能明显改变收敛轨迹,外汇与贵金属参数寻优属高风险实验,建议先用历史数据离线验证。

MQL5 / C++
      class=class="str">"cmt">//Xj = a [j].c [c];
      Xj = a [j].cB [c];
      class=class="str">"cmt">//Xi = a [i].c [c];
      Xi = a [i].cB [c];
      r = u.RNDprobab();
      class=class="str">"cmt">//Xi = Xi + C * r * (Xi - Xb) +
      class=class="str">"cmt">//     (class="num">1 - r) * (Xi - Xj);
      Xi = Xi + C * r * (Xb - Xi) +
           (class="num">1 - r) * (Xj - Xi);
      }
      }
      a [i].c [c] = u.SeInDiSp(Xi, rangeMin [c], rangeMax [c], rangeStep [c]);
      }
      }
      }
      consModel++;
      class="kw">return;
      }
      class=class="str">"cmt">// Decomposition -------------------------------------------------------------
      class="type">class="kw">double D = class="num">0.0;
      class="type">class="kw">double h = class="num">0.0;
      for (class="type">int i = class="num">0; i < popSize; i++)
      {
      D = class="num">3 * u.RNDprobab();
      h = u.RNDprobab() * (u.RNDprobab() < class="num">0.5 ? class="num">1 : -class="num">1);
      C = u.LevyFlightDistribution(levisPower);
      j = u.RNDminusOne(popSize);
      for (class="type">int c = class="num">0; c < coords; c++)
      {
      class="type">class="kw">double x = a [i].cB [c] + D * (C * a [i].cB [c] - h * a [j].c [c]);
      a [i].c [c] = u.SeInDiSp(x, rangeMin [c], rangeMax [c], rangeStep [c]);
      }
      }
      consModel = class="num">0;
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到算法在历史行情上的适应度曲线,你专注决策而非手算迭代。

常见问题

草对应当前种群中最差的解,草食动物是平均质量的解,肉食动物代表较优解;信息从草流向肉食动物以提升整体种群质量。
分解阶段把当前解相对全局最优做高斯散布,相当于把养分回洒搜索空间,帮助逃离局部最优并探索新区域。
可以,小布内置的AIGC看板能加载品种页历史数据并可视化适应度演化,省去自建回测框架的重复劳动。
莱维分布提供长尾随机步长负责探索,确定性更新规则利用已知优解负责利用,两者交替平衡搜索。
它显式建模分解者角色与养分循环,用高斯分解重分配信息,而不止依赖捕食者-猎物二元互动。