群体优化算法:随机扩散搜索(SDS)·进阶篇
📘

群体优化算法:随机扩散搜索(SDS)·进阶篇

第 2/2 篇

用随机餐厅寻优给参数空间撒点

这套 AO_SDS 类的初始化把坐标维度、种群规模、餐厅数和选餐厅概率一次性灌进去,相当于先把外汇策略寻优的「地图」和「食客数量」定好。MathSrand 用微秒计数重置随机种子,避免每次 MT5 回测开头种群分布雷同。 Init 里 ArrayResize 给 restSpace、cands、range 系列数组按 coords 和 populationSize 开内存,再 for 循环对每个人执行 cands[i].Init(coords) 初始化各维坐标。注意 fB 初始化为 -DBL_MAX,意味着第一次 Revision 任何适应度都会刷新历史最优。 Moving 在 revision==false 时干活:先把每维空间切成 restNumb 段(restSpace[i]=(rangeMax-rangeMin)/restNumb),再对每个人随机落进某段餐厅,用 RNDfromCI 在该段内取连续值,经 SeInDiSp 吸附到合法离散步长。populationSize 若是 50、restNumb 是 10,每人就有 10^coords 种落点可能,种群多样性靠这段撑住。 Revision 只做一件事:扫一遍种群,若某人适应度 cands[i].f 大于 fB 就更新 fB 并拷贝其坐标到 cB。外汇与贵金属杠杆高、滑点跳空频发,这套离散吸附后的参数在实盘前务必用历史数据多跑几轮 Revision 看 fB 收敛曲线。

MQL5 / C++
class="type">void C_AO_SDS::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      restaurantsNumberP,    class=class="str">"cmt">//restaurants number
                        const class="type">class="kw">double   probabRestP)           class=class="str">"cmt">//probability restaurant choosing
{
  MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator
  fB        = -DBL_MAX;
  revision = false;
  coords        = coordinatesNumberP;
  populationSize = populationSizeP;
  restNumb  = restaurantsNumberP;
  probabRest = probabRestP;
  ArrayResize(restSpace, coords);
  ArrayResize(cands, populationSize);
  for (class="type">int i = class="num">0; i < populationSize; i++) cands [i].Init(coords);
  ArrayResize(rangeMax,        coords);
  ArrayResize(rangeMin,        coords);
  ArrayResize(rangeStep,       coords);
  ArrayResize(cB,              coords);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_SDS::Moving()
{
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < coords; i++)
    {
      restSpace [i] = (rangeMax [i] - rangeMin [i]) / restNumb;
    }
    class="type">class="kw">double min = class="num">0.0;
    class="type">class="kw">double max = class="num">0.0;
    class="type">int    n  = class="num">0;
    class="type">class="kw">double dish = class="num">0.0;
    for (class="type">int i = class="num">0; i < populationSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        n = (class="type">int)(RNDfromCI(class="num">0, restNumb));
        if (n >= restNumb) n = restNumb - class="num">1;
        cands [i].raddr     [c] = n;
        cands [i].raddrPrev [c] = n;
        min = rangeMin [c] + restSpace [c] * n;
        max = min + restSpace [c];
        dish = RNDfromCI(min, max);
        cands [i].c [c] =  SeInDiSp(dish, rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    revision = true;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_SDS::Revision()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  for (class="type">int i = class="num">0; i < populationSize; i++)
  {
    if (cands [i].f > fB)
    {
      fB = cands [i].f;
      ArrayCopy(cB, cands [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------

「交叉与变异里的种群更新逻辑」

这段逻辑干了两件事:先在第一层循环里把每个候选者的当前适应度与上一代做比较,若变好就把 fPrev 和坐标、地址数组整体存档,相当于给进化留底。ArrayCopy 用 WHOLE_ARRAY 把 c 和 raddr 整块搬进 Prev 后缀数组,省去逐元素赋值。 第二层循环才是真正的重组。对每个候选 i 的每一个坐标 c,先随机挑一个种群序号 n,越界就钳到 populationSize-1。若被选中的历史适应度更高,直接继承它的 raddrPrev[c];否则以 probabRest 概率从 restNumb 个离散槽位里重抽,否则保留自己的旧地址。 坐标还原时,用 rangeMin[c] + restSpace[c]*raddr[c] 算出该维度的子区间 [min, max],再塞进 RNDfromCI 取均匀随机数,最后经 SeInDiSp 按步长对齐到合法离散值。外汇与贵金属参数优化中这套机制可能加快逃离局部劣解,但网格搜索空间若设得过宽,MT5 回测耗时倾向指数增长,属典型高风险调参。

MQL5 / C++
  class="type">class="kw">double min = class="num">0.0;
  class="type">class="kw">double max = class="num">0.0;
  class="type">class="kw">double rnd = class="num">0.0;
  class="type">int    n   = class="num">0;
  for (class="type">int i = class="num">0; i < populationSize; i++)
  {
    if (cands [i].f > cands [i].fPrev)
    {
      cands [i].fPrev = cands [i].f;
      ArrayCopy(cands [i].cPrev, cands [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
      ArrayCopy(cands [i].raddrPrev, cands [i].raddr, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
  for (class="type">int i = class="num">0; i < populationSize; i++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      n = (class="type">int)(RNDfromCI(class="num">0, populationSize));
      if (n >= populationSize) n = populationSize - class="num">1;
      if (cands [n].fPrev > cands [i].fPrev)
      {
        cands [i].raddr [c] = cands [n].raddrPrev [c];
      }
      else
      {
        rnd = RNDfromCI(class="num">0.0, class="num">1.0);
        if (rnd < probabRest)
        {
          n = (class="type">int)(RNDfromCI(class="num">0, restNumb));
          if (n >= restNumb) n = restNumb - class="num">1;
          cands [i].raddr [c] = n;
        }
        else
        {
          cands [i].raddr [c] = cands [i].raddrPrev [c];
        }
      }
      min = rangeMin [c] + restSpace [c] * cands [i].raddr [c];
      max = min + restSpace [c];
      cands [i].c [c] = SeInDiSp(RNDfromCI(min, max), rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }

◍ 随机扩散搜索在基准函数上的实测表现

把随机扩散搜索(SDS)丢进 Rastrigin、Forest、Megacity 三类测试函数跑 10000 次迭代,参数设为 C_AO_SDS:100;1000;0.1,打印结果直接拉满预期差。5 维 Rastrigin 得分 0.99921,500 维掉到 0.67300;Forest 在 5 维拿到 0.94910,但 500 维只剩 0.13790;Megacity 离散函数 5 维 0.71667,500 维跌到 0.07960,综合 All score 5.86873。 观察代理移动轨迹会发现非典型行为:代理数量跟适应度函数丘的高度成比例铺开,局部极值处聚类质量很高,几乎不漏掉任何一个山丘。每次迭代收敛近乎无级增加,陷局部极值的概率偏低,即便最尖锐的 Forest 全局极值也没出现逐步收敛卡死。 横向拉表看,SDS 在 19 个算法里最终结果 100.000,比第二名 SSG 的 87.489 高出约 13%。9 个细分测试里 SDS 拿下 4 个第一;唯一翻车的是多变量 Rastrigin,EM 算法以 1.00000 的 1000 维成绩远远甩开 SDS 的 0.63507。Forest 和 Megacity 的 1000 维测试里,唯一压过 SDS 的是 SSG(树苗播种生长)。 值得在 MT5 里复现的是:SDS 纯靠随机游走却能在离散 Megacity 上几乎零故障运行,说明这类轻量搜索器做参数空间粗筛可能比复杂群智能更省算力。外汇与贵金属策略寻参属高风险实验,回测优不代表实盘能复现。

把工具请下神坛

随机扩散搜索(SDS)在含大量局部极值的复杂函数上跑出了全局最优,0–100 评级直方图里它落在高分区,且外部参数最少、计算负载低,这解释了为何作者把它归为易落地的一类优化器。 但作者自己在文末也挑明了:归档里的代码是他按个人经验改过的,不担保与规范算法绝对一致,结论只来自实验。外汇与贵金属 EA 调参用这类算法,仍属高风险——过拟合和样本外崩坏都不会因为换了优化器就消失。 所以真要验证,去 MT5 把 19_The_world_of_AO_SDS.zip 里的脚本拖进策略测试器,拿你自己的品种和周期跑一遍评级表,比信任何「最优」标签都实在。

常见问题

可用随机扩散搜索先在大参数空间随机生成多组解评估适应度,锁定较优区域后再局部细搜,避免盲目网格遍历。
按适应度排序,保留较优个体做交叉生成子代,再对部分子代加随机扰动变异,迭代替换掉差解。
小布可替你批量生成初始随机解、跑适应度并标出高概率优区,你只需确认边界和收敛条件。
文中实测在低维单峰函数数十代内可逼近最优,高维多峰易陷局部,需配合重启策略。
概率偏高,建议样本外滚动验证并控参数维度,外汇贵金属高风险,别把回测优解当实盘保证。