种群优化算法:社群进化(ESG)·进阶篇
🧬

种群优化算法:社群进化(ESG)·进阶篇

(2/3)·单种群跑不出的多峰解,靠多个独立社群并行探索与经验交换才可能拿下

含代码示例偏理论 第 2/3 篇
把 ESG 当成单一种群来跑,很容易陷在同一个局部最优里出不来。多数人没意识到,群体间不交换经验,就等于主动放弃了对参数空间其他区域的覆盖。这篇接着把多种群结构拆开讲,别再拿旧框架硬套新算法。

粒子群初始化时的分组与余数摊派

这段 C_AO_ESG::Init 做的是进化群算法的启动配置:把总种群按 groups 拆成多个子群,并给每个坐标维度预留 rangeMax / rangeMin / rangeStep 数组。 核心分摊逻辑在 lost 变量:particles = popSize / groups 先算出每群基础人数,lost = popSize - particles * groups 是除不尽的余数。若 lost>0,就用 while 循环从 pos=0 起逐个子群 +1,绕回 groups 归零,直到 lost 消为零。 随机数种子用 MathSrand((int)GetMicrosecondCount()) 重置,避免每次回测群体轨迹雷同;fB 初始化为 -DBL_MAX 代表尚未记录全局最优适应度。外汇与贵金属品种上跑这套,滑点与点差会显著干扰适应度评估,属于高风险验证环境。 开 MT5 把这段代码贴进 EA 的 Init,故意设 popSize=53、groups=5,就能看到 lost=3 被摊进前 3 个子群,每群变 11/11/11/10/10,可直接在监视窗口核对 partInSwarms。

MQL5 / C++
class="kw">private: class="type">class="kw">double Scale(class="type">class="kw">double In, class="type">class="kw">double InMIN, class="type">class="kw">double InMAX, class="type">class="kw">double OutMIN, class="type">class="kw">double OutMAX, class="type">bool revers);
class="kw">private: class="type">class="kw">double PowerDistribution(const class="type">class="kw">double In, const class="type">class="kw">double outMin, const class="type">class="kw">double outMax, const class="type">class="kw">double power);
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ESG::Init(const class="type">int      coordinatesNumberP,   class=class="str">"cmt">//coordinates number
                                const class="type">int      populationSizeP,     class=class="str">"cmt">//population size
                                const class="type">int      groupsP,             class=class="str">"cmt">//number of groups
                                const class="type">class="kw">double   groupRadiusP,        class=class="str">"cmt">//group radius
                                const class="type">class="kw">double   expansionRatioP,     class=class="str">"cmt">//expansion ratio
                                const class="type">class="kw">double   powerP)
{
   MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator
   fB       = -DBL_MAX;
   revision = false;
   coords        = coordinatesNumberP;
   popSize       = populationSizeP;
   groups        = groupsP;
   groupRadius   = groupRadiusP;
   expansionRatio = expansionRatioP;
   power         = powerP;
   class=class="str">"cmt">//----------------------------------------------------------------------------
   class="type">int partInSwarms [];
   ArrayResize(partInSwarms, groups);
   class="type">int particles = popSize / groups;
   ArrayInitialize(partInSwarms, particles);
   class="type">int lost = popSize - particles * groups;
   if (lost > class="num">0)
   {
      class="type">int pos = class="num">0;
      class="kw">while (true)
      {
         partInSwarms [pos]++;
         lost--;
         pos++;
         if (pos >= groups) pos = class="num">0;
         if (lost == class="num">0) break;
      }
   }
   class=class="str">"cmt">//----------------------------------------------------------------------------
   ArrayResize(rangeMax,  coords);
   ArrayResize(rangeMin,  coords);
   ArrayResize(rangeStep, coords);
   ArrayResize(cB,        coords);
   ArrayResize(gr,        groups);
   for (class="type">int s = class="num">0; s < groups; s++) gr [s].Init(coords, partInSwarms [s]);
   ArrayResize(a, popSize);
   for (class="type">int i = class="num">0; i < popSize; i++) a [i].Init(coords);
}

「群体初始化与迭代修正的双段逻辑」

在 MT5 里跑这套进化式参数搜索,第一步是 Moving() 把群体空间铺开。它只在 revision 为 false 时执行一次:先给每个 group 定下圆心 cB 与搜索半径 sRadius,再按 PowerDistribution 在半径内撒个体。半径默认取 (rangeMax-rangeMin)*groupRadius,越界就截断到参数边界,这一步直接决定后续寻优会不会漏掉边角解。 Revision() 则是每代必跑的修正环。它先扫一遍 popSize 个个体,把适应度 a[i].f 大于全局最优 fB 的拎出来,用 ArrayCopy 把坐标写进 cB;随后逐组比对,若组内有人超过 gr[s].fB 就更新组最优。关键在末行:整组一代没进步,sRadius 就乘 expansionRatio 扩张——外汇与贵金属波动率高,这组扩张系数设大了可能过早发散,设小了则容易卡在局部,建议先在 EURUSD 的 M15 上用 groupRadius=0.1、expansionRatio=1.2 手测。 两个函数靠 revision 标志位切换,初始化完置 true 后 Moving 不再触发,所有算力留给 Revision 做代际逼近。想验证的话,直接把下面代码挂进 EA 的 OnTick 前段,打印 gr[0].sRadius 就能看到无改进时半径逐代放大的轨迹。

MQL5 / C++
class="type">void C_AO_ESG::Moving()
{
  if (!revision)
  {
    class="type">int    cnt       = class="num">0;
    class="type">class="kw">double coordinate = class="num">0.0;
    class="type">class="kw">double radius     = class="num">0.0;
    class="type">class="kw">double min        = class="num">0.0;
    class="type">class="kw">double max        = class="num">0.0;
    class=class="str">"cmt">//generate centers------------------------------------------------------
    for (class="type">int s = class="num">0; s < groups; s++)
    {
      gr [s].sRadius = groupRadius;
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        coordinate    = RNDfromCI(rangeMin [c], rangeMax [c]);
        gr [s].cB [c] = SeInDiSp(coordinate, rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    class=class="str">"cmt">//generate individuals of groups--------------------------------------------
    for (class="type">int s = class="num">0; s < groups; s++)
    {
      for (class="type">int p = class="num">0; p < gr [s].sSize; p++)
      {
        for (class="type">int c = class="num">0; c < coords; c++)
        {
          radius  = (rangeMax [c] - rangeMin [c]) * gr [s].sRadius;
          min     = gr [s].cB [c] - radius;
          max     = gr [s].cB [c] + radius;
          if (min < rangeMin [c]) min = rangeMin [c];
          if (max > rangeMax [c]) max = rangeMax [c];
          coordinate    = PowerDistribution(gr [s].cB [c], min, max, power);
          a [cnt].c [c] = SeInDiSp(coordinate, rangeMin [c], rangeMax [c], rangeStep [c]);
        }
        cnt++;
      }
    }
    revision = true;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ESG::Revision()
{
  class=class="str">"cmt">//update the best global solution
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
  class="type">int cnt = class="num">0;
  class="type">bool impr = false;
  for (class="type">int s = class="num">0; s < groups; s++)
  {
    impr = false;
    for (class="type">int p = class="num">0; p < gr [s].sSize; p++)
    {
      if (a [cnt].f > gr [s].fB)
      {
        gr [s].fB = a [cnt].f;
        ArrayCopy(gr [s].cB, a [cnt].c, class="num">0, class="num">0, WHOLE_ARRAY);
        impr = true;
      }
      cnt++;
    }
    if (!impr) gr [s].sRadius *= expansionRatio;

◍ 群体采样与经验交换的落地实现

上面的片段承接了分组初始化之后两件具体的事:按组半径撒点生成个体,以及做跨组经验借用。注意组半径被硬截断在 0.5——这意味着任一维度的搜索跨度最多占到该维全范围的二分之一,避免单组过早垄断解空间。 生成个体时,对每个坐标先按 (rangeMax-rangeMin)*sRadius 算出半径,再以组中心 cB 为基准向两侧取 min/max,越界就夹回全局范围。坐标本身不是均匀撒的,而是走 PowerDistribution(...) 再经 SeInDiSp(...) 离散化,所以密度会偏向中心,外围样本偏稀。 经验交换那段更有意思:posSw 用 RNDfromCI(0, groups) 取整,越界就强制 groups-1,然后直接把 a[cnt].c[c] 覆盖成 gr[posSw].cB[c]。注释里那行按适应度比较被删了,等于无条件照搬别组中心,属于激进信息共享,实盘调参时建议把判断条件加回来,否则可能让已收敛的组被随机带偏。 S_Agent 结构里 c 与 cMain 分开存,f/fMain 初值给 -DBL_MAX,说明每代都重算适应度,不继承历史最优。Revision() 开头那段就是扫一遍种群,遇到 a[i].f > fB 就更新全局最优并 ArrayCopy 坐标,逻辑直白但依赖 f 已被外层算好。 外汇与贵金属波动剧烈,这类群体搜索若直接接实盘信号,参数敏感性高,请先在 MT5 策略测试器跑历史数据验证稳定性。

MQL5 / C++
else        gr [s].sRadius  = groupRadius;
if (gr [s].sRadius > class="num">0.5) gr [s].sRadius = class="num">0.5;
}
class=class="str">"cmt">//generate individuals of groups----------------------------------------------
class="type">class="kw">double coordinate = class="num">0.0;
class="type">class="kw">double radius     = class="num">0.0;
class="type">class="kw">double min        = class="num">0.0;
class="type">class="kw">double max        = class="num">0.0;
cnt = class="num">0;
for (class="type">int s = class="num">0; s < groups; s++)
{
  for (class="type">int p = class="num">0; p < gr [s].sSize; p++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      if (RNDfromCI(class="num">0.0, class="num">1.0) < class="num">1.0)
      {
        radius  = (rangeMax [c] - rangeMin [c]) * gr [s].sRadius;
        min     = gr [s].cB [c] - radius;
        max     = gr [s].cB [c] + radius;
        if (min < rangeMin [c]) min = rangeMin [c];
        if (max > rangeMax [c]) max = rangeMax [c];
        coordinate       = PowerDistribution(gr [s].cB [c], min, max, power);
        a [cnt].c [c] = SeInDiSp(coordinate, rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    cnt++;
  }
}
class=class="str">"cmt">//exchange of experience----------------------------------------------------------------
cnt = class="num">0;
for (class="type">int s = class="num">0; s < groups; s++)
{
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    class="type">int posSw = (class="type">int)RNDfromCI(class="num">0, groups);
    if (posSw >= groups) posSw = groups - class="num">1;
    class=class="str">"cmt">//if (sw [posSw].fB > sw [s].fB)
    {
      a [cnt].c [c] = gr [posSw].cB [c];
    }
  }
  cnt += gr [s].sSize;
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="kw">struct S_Agent
{
  class="type">void Init(const class="type">int coords)
  {
    ArrayResize(c,     coords);
    ArrayResize(cMain, coords);
    f     = -DBL_MAX;
    fMain = -DBL_MAX;
  }
  class="type">class="kw">double c     []; class=class="str">"cmt">//coordinates
  class="type">class="kw">double cMain []; class=class="str">"cmt">//coordinates
  class="type">class="kw">double f;        class=class="str">"cmt">//fitness
  class="type">class="kw">double fMain;    class=class="str">"cmt">//fitness
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ESG::Revision()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">//Update the best global solution
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);

群体半径与经验交换的迭代逻辑

这段代码是群体智能优化里每代收尾的两件事:先拿本次适应度刷新个体与族群的最优记忆,再据此重算搜索半径并生成下一批候选。外汇与贵金属参数寻优属高风险实验,回测漂亮不等于实盘能复现,任何结论都只是概率倾向。 个体层先做记忆更新:当临时适应度 a[p].f 超过历史最优 a[p].fMain,就把当前坐标数组拷进 cMain。族群层用 cnt 游标遍历每组,若组内有人刷新 gr[s].fB 则记为新标杆并置 impr=true;整组无改进时半径按 expansionRatio 放大,有改进则重置为 groupRadius,且硬上限锁在 0.5,避免跨维乱跳。 下一批个体生成时,每个坐标有 0.6 概率围绕族群最优 cB[c] 在 ±radius 内做幂分布抽样,radius 由参数区间跨度乘 sRadius 得出,越界则夹回 [rangeMin,rangeMax]。最后经验交换段给每个个体随机挑一个同伴,以 copyProb 概率抄对方坐标——这段虽被截断,但机制就是群体间信息渗漏。 把 sRadius 上限 0.5 和 0.6 抽样概率直接丢进 MT5 跑一遍,看族群收敛速度是变快还是过早散开,比读十遍文档管用。

MQL5 / C++
   }
   }
   class=class="str">"cmt">//----------------------------------------------------------------------------
   class=class="str">"cmt">//update agents
   for (class="type">int p = class="num">0; p < popSize; p++)
   {
      if (a [p].f > a [p].fMain)
      {
         a [p].fMain = a [p].f;
         ArrayCopy(a [p].cMain, a [p].c, class="num">0, class="num">0, WHOLE_ARRAY);
      }
   }
   class=class="str">"cmt">//----------------------------------------------------------------------------
   class="type">int cnt = class="num">0;
   class="type">bool impr = false;
   for (class="type">int s = class="num">0; s < groups; s++)
   {
      impr = false;
      for (class="type">int p = class="num">0; p < gr [s].sSize; p++)
      {
         if (a [cnt].f > gr [s].fB)
         {
            gr [s].fB = a [cnt].f;
            ArrayCopy(gr [s].cB, a [cnt].c, class="num">0, class="num">0, WHOLE_ARRAY);
            impr = true;
         }
         cnt++;
      }
      if (!impr) gr [s].sRadius *= expansionRatio;
      else       gr [s].sRadius  = groupRadius;
      if (gr [s].sRadius > class="num">0.5) gr [s].sRadius = class="num">0.5;
   }
   class=class="str">"cmt">//generate individuals of groups----------------------------------------------
   class="type">class="kw">double coordinate = class="num">0.0;
   class="type">class="kw">double radius     = class="num">0.0;
   class="type">class="kw">double min        = class="num">0.0;
   class="type">class="kw">double max        = class="num">0.0;
   cnt = class="num">0;
   for (class="type">int s = class="num">0; s < groups; s++)
   {
      for (class="type">int p = class="num">0; p < gr [s].sSize; p++)
      {
         for (class="type">int c = class="num">0; c < coords; c++)
         {
            if (RNDfromCI(class="num">0.0, class="num">1.0) < class="num">0.6)
            {
               radius       = (rangeMax [c] - rangeMin [c]) * gr [s].sRadius;
               min          = gr [s].cB [c] - radius;
               max          = gr [s].cB [c] + radius;
               if (min < rangeMin [c]) min = rangeMin [c];
               if (max > rangeMax [c]) max = rangeMax [c];
               coordinate   = PowerDistribution(gr [s].cB [c], min, max, power);
               a [cnt].c [c] = SeInDiSp(coordinate, rangeMin [c], rangeMax [c], rangeStep [c]);
            }
         }
         cnt++;
      }
   }
   class=class="str">"cmt">//exchange of experience----------------------------------------------------------------
   cnt = class="num">0;
   for (class="type">int p = class="num">0; p < popSize; p++)
   {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
         class="type">int pos = (class="type">int)RNDfromCI(class="num">0, popSize);
         if (pos >= popSize) pos = popSize - class="num">1;
         if (RNDfromCI(class="num">0.0, class="num">1.0) < copyProb)
         {
把多群迭代交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多社群收敛路径的实时概览,你只管判断哪条经验值得采信。

常见问题

多种群强调多个独立种群并行且可交换最优信息,多群体更偏向社群内份子互动合作;前者在高层次模拟群体普遍行为,后者重心在低层个体协作。
群体份子追随主导行为模型但有分布偏差,当某个更自适的行为形态出现,它就变成新中枢,群体随之转向稳定模型搜索。
局部经验存各群自身最优解,全局经验存跨群总体最佳分数;两者结合让群体既深耕区域也借力全局避免重复探索。
目前小布内置的是收敛概览与诊断视图,原始策略代码需在终端自行部署,但看盘端可同步多群状态省去手动记录。
贵金属与外汇价差结构常随时段漂移,群体若能基于经验改移动策略,才可能在高风险环境下保住解的有效性。