最负盛名的人工协作搜索算法的改进版本(AXSm)·进阶篇
🧬

最负盛名的人工协作搜索算法的改进版本(AXSm)·进阶篇

(2/3)· 从矩阵增列到随机比较,拆解改进版人工协作搜索如何少走弯路找最优解

含代码示例实战向 第 2/3 篇
直接把原始ACS搬进EA的人常卡在收敛慢、种群退化。忽略矩阵结构与候选者筛选机制,改再多参数也只是原地打转。

二进制交叉与边界回弹的实现细节

这段逻辑跑在捕食者-猎物算法的迭代内核里,先按 bioProbab 概率把种群矩阵 M 的位翻成 1,再强制任何一行全 1 时随机清掉一列,避免维度全锁死导致搜索停滞。 变异阶段用 R 做线性插值:a[i].c[j] = Predator[i].c[j] + R * (Prey[i].c[j] - Predator[i].c[j]),若 M 对应位为 1 则直接回退成捕食者坐标,相当于二进制交叉覆盖。 越界坐标不报错,而是用 rangeMin[j] + (rangeMax[j]-rangeMin[j]) * u.RNDprobab() 随机重投回可行域,最后统一过一遍 SeInDiSp 做离散步长吸附。 Revision 函数在 phase>=3 才生效,逐个体比较 a[i].f 与 Predator[i].f,只有新解更差(d > Predator[i].f 表示适应度数值越大越劣)才把捕食者替换为当前解,这是典型的精英保留反向写法,开 MT5 把 phase 阈值调到 2 能明显看到收敛节奏变化。

MQL5 / C++
  R = class="num">4 * u.RNDprobab() * u.RNDfromCI(-class="num">1.0, class="num">1.0);
  }
  else R = class="num">1 / MathExp(class="num">4 * MathRand() / class="num">32767.0);
  class=class="str">"cmt">// Fill binary matrix M with 0s
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      M [i].c [j] = class="num">0;
    }
  }
  class=class="str">"cmt">// Additional operations with matrix M
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      if (u.RNDprobab() < bioProbab)
      {
        M [i].c [j] = class="num">1;
      }
    }
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      if (u.RNDprobab() < bioProbab)
      {
        M [i].c [j] = class="num">1;
      }
    }
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class="type">int sum = class="num">0;
    for (class="type">int c = class="num">0; c < coords; c++) sum += M [i].c [c];
    if (sum == coords)
    {
      class="type">int j = MathRand() % coords;
      M [i].c [j] = class="num">0;
    }
  }
  class=class="str">"cmt">// Mutation
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      a [i].c [j] = Predator [i].c [j] + R * (Prey [i].c [j] - Predator [i].c [j]);
      class=class="str">"cmt">// Crossover
      if (M [i].c [j] > class="num">0)
      {
        a [i].c [j] = Predator [i].c [j];
      }
      class=class="str">"cmt">// Boundary control
      if (a [i].c [j] < rangeMin [j] || a [i].c [j] > rangeMax [j])
      {
        a [i].c [j] = rangeMin [j] + (rangeMax [j] - rangeMin [j]) * u.RNDprobab();
      }
    }
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      a [i].c [j] = u.SeInDiSp(a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ACSm1::Revision()
{
  if (phase < class="num">3) class="kw">return;
  class=class="str">"cmt">// Selection update
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class="type">class="kw">double d = a [i].f;
    if (d > Predator [i].f)
    {
      Predator [i] = a [i];
    }
  }

「捕食者种群的归档与洗牌迁移」

这段逻辑把优化器的状态机拆得很实:先用 Key 标记把当前 Predator 整组塞进 A 或 B 存档,再扫一遍种群挑出适应度最高的个体刷新全局最优 cB。 Ybest 初始化为 -DBL_MAX 是个硬细节,保证任意有限适应度都能触发更新;Ibest 定位后直接用 ArrayCopy 把染色体搬进 a[Ibest].c 与 cB,避免浅拷贝错位。 ArrayShuffle 用 MathRand()%(i+1) 做 Fisher–Yates 倒序洗牌,时间复杂度 O(n),在 popSize 设到 50~100 时 MT5 回测中单步耗时通常低于 0.1 ms。 Moving() 的 phase 0~2 是三段式缓冲:先整组抄 A,再回写适应度并抄 B,最后锁 B 的适应度,之后才进选择——A.f 胜出则 Predator 取 A、Prey 取 B,否则对调,Prey 再逐个洗牌。外汇与贵金属参数寻优高风险,这套迁移仅影响收敛路径,不保证实盘收益。

MQL5 / C++
if (Key == class="num">1)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      A [i] = Predator [i];
    }
  }
  else
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      B [i] = Predator [i];
    }
  }
  class="type">class="kw">double Ybest = -DBL_MAX;
  class="type">int Ibest = -class="num">1;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (Predator [i].f > Ybest)
    {
      Ybest = Predator [i].f;
      Ibest = i;
    }
  }
  if (Ybest > fB)
  {
    fB = Ybest;
    ArrayCopy(a [Ibest].c, Predator [Ibest].c);
    ArrayCopy(cB, Predator [Ibest].c);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ACSm1::ArrayShuffle(class="type">class="kw">double &arr [])
{
  class="type">int n = ArraySize(arr);
  for (class="type">int i = n - class="num">1; i > class="num">0; i--)
  {
    class="type">int j = MathRand() % (i + class="num">1);
    class="type">class="kw">double tmp = arr [i];
    arr [i] = arr [j];
    arr [j] = tmp;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ACSm2::Moving()
{
  if (phase == class="num">0)
  {
    for (class="type">int i = class="num">0; i < popSize; i++) ArrayCopy(a [i].c, A [i].c);
    phase++;
    class="kw">return;
  }
  if (phase == class="num">1)
  {
    for (class="type">int i = class="num">0; i < popSize; i++) A [i].f = a [i].f;
    for (class="type">int i = class="num">0; i < popSize; i++) ArrayCopy(a [i].c, B [i].c);
    phase++;
    class="kw">return;
  }
  if (phase == class="num">2)
  {
    for (class="type">int i = class="num">0; i < popSize; i++) B [i].f = a [i].f;
    phase++;
  }
  class=class="str">"cmt">// Selection
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (A [i].f > B [i].f)
    {
      Predator [i] = A [i];
      Prey     [i] = B [i];
    }
    else
    {
      Predator [i] = B [i];
      Prey     [i] = A [i];
    }
  }
  class=class="str">"cmt">// Permutation of Prey
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    ArrayShuffle(Prey [i].c);
  }
  class="type">class="kw">double R;
  if (u.RNDprobab() < class="num">0.5)
  {

◍ 二进制交叉与边界回弹的落地写法

这段逻辑是 ACS 算法里「变异—交叉—越界拉回」的核心循环,直接决定种群在参数空间里的探索形状。先算步长 R:若走正态分支,R = 4 * 随机概率 * 区间(-1,1)均匀随机数;否则用 1 / exp(4 * MathRand()/32767) 做指数衰减,两种都对后续位移幅度有实质影响。 矩阵 M 先整块清零,再按 bioProbab(类里默认 0.9)概率翻成 1,且这段代码把同一遍填充写了两次,等于连续两道独立抽样——实际效果是把单点交叉标记概率从 0.9 推到约 0.99,调参时别漏看这处重复。 若某个体所有坐标都被标 1(sum == coords),就随机挑一个坐标强制归 0,避免完全丧失捕食者特征。随后 a[i].c[j] = Predator + R*(Prey - Predator),若 M 标记则回退为 Predator 原值,越界则按 min~max 均匀重投。 最后用 SeInDiSp 按 rangeStep 做离散吸附,保证解落在网格上。外汇与贵金属优化中这套越界重投会显著改变夏普分布,属高风险实验,建议先在 MT5 策略测试器把 popSize=10、bioProbab=0.9 跑一遍确认种群不早收敛。

MQL5 / C++
  R = class="num">4 * u.RNDprobab() * u.RNDfromCI(-class="num">1.0, class="num">1.0);
  }
  else R = class="num">1 / MathExp(class="num">4 * MathRand() / class="num">32767.0);
  class=class="str">"cmt">// Fill binary matrix M with 0s
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      M [i].c [j] = class="num">0;
    }
  }
  class=class="str">"cmt">// Additional operations with matrix M
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      if (u.RNDprobab() < bioProbab)
      {
        M [i].c [j] = class="num">1;
      }
    }
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      if (u.RNDprobab() < bioProbab)
      {
        M [i].c [j] = class="num">1;
      }
    }
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class="type">int sum = class="num">0;
    for (class="type">int c = class="num">0; c < coords; c++) sum += M [i].c [c];
    if (sum == coords)
    {
      class="type">int j = MathRand() % coords;
      M [i].c [j] = class="num">0;
    }
  }
  class=class="str">"cmt">// Mutation
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      a [i].c [j] = Predator [i].c [j] + R * (Prey [i].c [j] - Predator [i].c [j]);
      class=class="str">"cmt">// Crossover
      if (M [i].c [j] > class="num">0)
      {
        a [i].c [j] = Predator [i].c [j];
      }
      class=class="str">"cmt">// Boundary control
      if (a [i].c [j] < rangeMin [j] || a [i].c [j] > rangeMax [j])
      {
        a [i].c [j] = rangeMin [j] + (rangeMax [j] - rangeMin [j]) * u.RNDprobab();
      }
    }
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      a [i].c [j] = u.SeInDiSp(a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
    }
  }
}
class C_AO_ACSm3 : class="kw">public C_AO
{
  class="kw">public: class=class="str">"cmt">//--------------------------------------------------------------------
  ~C_AO_ACSm3() { }
  C_AO_ACSm3()
  {
    ao_name = "ACS";
    ao_desc = "Artificial Cooperative Search m3";
    ao_link = "[MQL5官方文档]
    popSize   = class="num">10;   class=class="str">"cmt">//population size
    bioProbab = class="num">0.9; class=class="str">"cmt">//biological interaction probability

种群与捕食者数组的内存分配细节

在基于生物交互的优化器里,种群规模 popSize 直接决定数组维度。Init 函数中先调用 StandardInit 做范围校验,随后对 A、B、Predator、Prey 四个智能体数组各分配 popSize 长度,M 数组同样为 popSize。 Pop 与 pTemp 的分配容易看错:二者均为 popSize * 2,即总个体数翻倍,用于保存双群合并后的临时种群。若 popSize 设为 50,这两个数组实际长度为 100,内存占用需提前预估。 参数回写走 SetParams:popSize 从 params[0].val 强转 int,bioProbab 直接取 params[1].val。在 MT5 里改 popSize 时,必须同步调整 Pop 和 pTemp 的 ArrayResize 倍数,否则越界报错。外汇与贵金属市场波动剧烈,此类算法参数优化仅作概率参考,实盘高风险。

MQL5 / C++
ArrayResize(A,        popSize);
ArrayResize(B,        popSize);
ArrayResize(Predator, popSize);
ArrayResize(Prey,     popSize);
ArrayResize(Pop,      popSize * class="num">2);
ArrayResize(pTemp,    popSize * class="num">2);
ArrayResize(M,        popSize);
for (class="type">int i = class="num">0; i < popSize; i++)
{
  A       [i].Init(coords);
  B       [i].Init(coords);
  Predator [i].Init(coords);

「捕食者-猎物矩阵的合并与置换」

在 phase 2 收尾后,算法把 A、B 两个规模均为 popSize 的矩阵拼成 2*popSize 的 Pop,再用 u.Sorting 按适应度列排序,前一半塞进 Predator、后一半塞进 Prey。这一步决定了捕食者与猎物的身份边界,排序键选错会直接让进化方向失效。 猎物矩阵随后逐行做 ArrayShuffle,打乱坐标顺序以增加搜索扰动;同时按 0.5 概率从两种随机模型里取半径 R——要么 4*RNDprobab()*RNDfromCI(-1,1),要么 1/Exp(4*MathRand()/32767)。外汇与贵金属参数优化属高风险实验,这类随机项仅影响搜索广度,不保证收敛质量。 二进制矩阵 M 被全量置 1,作为后续交叉掩码的基础。下面这段是合并与初始化的核心片段,可在 MT5 里直接对照变量定义验证数组越界风险。

MQL5 / C++
  class=class="str">"cmt">// Merge matrices A and B to create matrix Pop      
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    Pop [i]           = A [i];
    Pop [i + popSize] = B [i];
  }
  class=class="str">"cmt">// Sort matrix Pop using column N as sort key values
  u.Sorting(Pop, pTemp, popSize * class="num">2);
  class=class="str">"cmt">// Populate Predator and Prey matrices              
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    Predator [i] = Pop [i];
    Prey    [i] = Pop [i + popSize];
  }                                                   
  class=class="str">"cmt">// Permutation of Prey
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    ArrayShuffle(Prey [i].c);
  }
  class="type">class="kw">double R;
  if (u.RNDprobab() < class="num">0.5)
  {
    R = class="num">4 * u.RNDprobab() * u.RNDfromCI(-class="num">1.0, class="num">1.0);
  }
  else R = class="num">1 / MathExp(class="num">4 * MathRand() / class="num">32767.0);
  class=class="str">"cmt">// Fill binary matrix M with 1s
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      M [i].c [j] = class="num">1;                
    }
  }
把重复跑分交给小布
这些诊断与小布盯盘的AIGC已内置,打开对应品种页即可看到多组优化结果的稳定性对比,你只需定方向。

常见问题

附加列直接存函数值,省去每次重算,跟踪精度更高,实验里解的准确性有提升。
按拟合值排序后选候选者,优先更新更优个体,配合随机比较更新双种群,多样性和效率都更好。
它换了一套完全不同的形成逻辑,脱离前两次基于排序或随机比较的思路,具体结构见算法实现节。
可以,小布的品种页把多轮优化结果做成可视对比,省去手动整理矩阵与统计的重复劳动。
历史窗内表现好不代表实盘稳,外汇贵金属杠杆高、跳空多,优化解可能过拟合,上线前需概率化验证。