种群优化算法:入侵杂草优化(IWO)·进阶篇
📘

种群优化算法:入侵杂草优化(IWO)·进阶篇

第 2/2 篇

入侵杂草算法的初始化与首次播种

C_AO_IWO 类的 Init 方法负责把入侵杂草优化(IWO)所需的全部参数与数组一次性备好。调用前你要先决定坐标维度、杂草数、种子数,以及每株杂草播撒种子数的上下限——这些直接决定后续寻优的搜索广度。 代码里有一处硬约束值得注意:若 numberWeeds * minNumberSeeds 超过了总种子数,杂草数会被强制改写为 numberSeeds / minNumberSeeds。比如设了 50 株杂草、最小种子数 3,但总种子只有 100,实际杂草数会被砍到 33,避免数组越界。 首次播种由 sowing 标志控制,仅在未播种时执行。每颗种子在每个坐标维度上调用 RNDfromCI(rangeMin[c], rangeMax[c]) 取随机初值,相当于在给定区间内撒下初始解。外汇与贵金属参数优化属高风险操作,回测表现好不代表实盘概率占优,请先在 MT5 策略测试器小样本验证。 下面把 Init 与首次播种的核心代码逐行拆开,方便你直接抄进 EA 框架改参数跑。

MQL5 / C++
class="type">void C_AO_IWO::Init(class="kw">const class="type">int      coordinatesP,        class=class="str">"cmt">//Number of coordinates
                        class="kw">const class="type">int      numberSeedsP,        class=class="str">"cmt">//Number of seeds
                        class="kw">const class="type">int      numberWeedsP,        class=class="str">"cmt">//Number of weeds
                        class="kw">const class="type">int      maxNumberSeedsP,     class=class="str">"cmt">//Maximum number of seeds per weed
                        class="kw">const class="type">int      minNumberSeedsP,     class=class="str">"cmt">//Minimum number of seeds per weed
                        class="kw">const class="type">class="kw">double   maxDispersionP,      class=class="str">"cmt">//Maximum dispersion
                        class="kw">const class="type">class="kw">double   minDispersionP,      class=class="str">"cmt">//Minimum dispersion
                        class="kw">const class="type">int      maxIterationP)      class=class="str">"cmt">//Maximum iterations
{
  MathSrand(GetTickCount());                  class=class="str">"cmt">// 用系统节拍数做随机种子,保证每次运行序列不同
  sowing = class="kw">false;                               class=class="str">"cmt">// 标记尚未播种
  fB       = -DBL_MAX;                          class=class="str">"cmt">// 最优适应度初始化为负极大
  coordinates      = coordinatesP;              class=class="str">"cmt">// 坐标维度(参数个数)
  numberSeeds      = numberSeedsP;              class=class="str">"cmt">// 种子总数
  numberWeeds      = numberWeedsP;              class=class="str">"cmt">// 杂草株数初值
  maxNumberSeeds   = maxNumberSeedsP;           class=class="str">"cmt">// 单株最大种子数
  minNumberSeeds   = minNumberSeedsP;           class=class="str">"cmt">// 单株最小种子数
  maxDispersion    = maxDispersionP;            class=class="str">"cmt">// 最大散布半径
  minDispersion    = minDispersionP;            class=class="str">"cmt">// 最小散布半径
  maxIteration     = maxIterationP;             class=class="str">"cmt">// 最大迭代次数
  if (minNumberSeeds < class="num">1) minNumberSeeds = class="num">1;   class=class="str">"cmt">// 最小种子数保底为1
  if (numberWeeds * minNumberSeeds > numberSeeds) numberWeeds = numberSeeds / minNumberSeeds; class=class="str">"cmt">// 越界则重算杂草数
  else numberWeeds = numberWeedsP;              class=class="str">"cmt">// 否则维持传入值
  totalNumWeeds    = numberWeeds + numberSeeds; class=class="str">"cmt">// 杂草+种子总容量
  ArrayResize(rangeMax,  coordinates);         class=class="str">"cmt">// 各维上限数组
  ArrayResize(rangeMin,  coordinates);         class=class="str">"cmt">// 各维下限数组
  ArrayResize(rangeStep, coordinates);         class=class="str">"cmt">// 各维步长数组
  ArrayResize(vec,       coordinates);         class=class="str">"cmt">// 临时向量
  ArrayResize(cB,        coordinates);         class=class="str">"cmt">// 最优坐标缓存
  ArrayResize(weeds,  totalNumWeeds);          class=class="str">"cmt">// 杂草结构体数组
  ArrayResize(weedsT, totalNumWeeds);          class=class="str">"cmt">// 杂草临时数组
  ArrayResize(seeds,  numberSeeds);            class=class="str">"cmt">// 种子结构体数组
  for (class="type">int i = class="num">0; i < numberWeeds; i++)         class=class="str">"cmt">// 初始化每株杂草
  {
    ArrayResize(weeds  [i].c, coordinates);    class=class="str">"cmt">// 杂草坐标数组
    ArrayResize(weedsT [i].c, coordinates);    class=class="str">"cmt">// 杂草临时坐标
    weeds [i].f = -DBL_MAX;                     class=class="str">"cmt">// 杂草适应度负极大
    weeds [i].s = class="num">0;                            class=class="str">"cmt">// 杂草种子计数0
  }
  for (class="type">int i = class="num">0; i < numberSeeds; i++)         class=class="str">"cmt">// 初始化每颗种子
  {
    ArrayResize(seeds [i].c, coordinates);     class=class="str">"cmt">// 种子坐标数组
    seeds [i].s = class="num">0;                            class=class="str">"cmt">// 种子步长0
  }
  ArrayResize(ind, totalNumWeeds);             class=class="str">"cmt">// 索引数组
  ArrayResize(val, totalNumWeeds);             class=class="str">"cmt">// 值数组
  ArrayResize(wf,  numberWeeds);               class=class="str">"cmt">// 杂草适应度数组
}
class=class="str">"cmt">//the first sowing of seeds
if (!sowing)
{
  fB = -DBL_MAX;                                class=class="str">"cmt">// 重置最优适应度
  for (class="type">int s = class="num">0; s < numberSeeds; s++)         class=class="str">"cmt">// 遍历所有种子
  {
    for (class="type">int c = class="num">0; c < coordinates; c++)       class=class="str">"cmt">// 遍历各坐标维
    {
      seeds [s].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]); class=class="str">"cmt">// 在维度区间内随机初始化种子坐标
    }
  }
}

「杂草适应度越高撒种越密的实作」

入侵杂草算法里,基础撒种只保证每株杂草先落 minNumberSeeds 粒,之后要按适应度把剩余 numberSeeds - pos 粒分配出去。代码用 wf 数组把杂草适应度映射成概率区间:最优杂草 wf[0].end 减去最差杂草适应度作为宽度,越优的区间越宽,随机命中概率越高。 注意最后一项的特殊处理:最差杂草的区间宽度被压到 (weeds[f-1].f - weeds[f].f) * 0.1,也就是只留 10% 的差距补偿,避免最差草完全分不到种。若某株已达 maxNumberSeeds 上限,while 循环会从 weedsPos 向后绕回找还能接收的草,绕一圈回 0 就强制塞进去。 坐标扰动统一用 r = RNDfromCI(-1,1) 再立方,r*r*r 把大部分种子压在母株附近,偶尔跳出远距离。dispersion 随迭代从 maxDispersion 线性衰减到 minDispersion,早期探索广、后期收敛紧。外汇与贵金属参数优化用这套,过拟合风险高,建议拿 MT5 策略测试器跑多品种验证。

MQL5 / C++
class="type">int    pos = class="num">0;
class="type">class="kw">double r  = class="num">0.0;
class="type">class="kw">double dispersion = ((maxIteration - iter) / (class="type">class="kw">double)maxIteration) * (maxDispersion - minDispersion) + minDispersion;
for (class="type">int w = class="num">0; w < numberWeeds; w++)
{
  weeds [w].s = class="num">0;
  for (class="type">int s = class="num">0; s < minNumberSeeds; s++)
  {
    for (class="type">int c = class="num">0; c < coordinates; c++)
    {
      r = RNDfromCI(-class="num">1.0, class="num">1.0);
      r = r * r * r;
      seeds [pos].c [c] = weeds [w].c [c] + r * vec [c] * dispersion;
      seeds [pos].c [c] = SeInDiSp(seeds [pos].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
    pos++;
    weeds [w].s++;
  }
}
wf [class="num">0].start = weeds [class="num">0].f;
wf [class="num">0].end   = wf [class="num">0].start + (weeds [class="num">0].f - weeds [numberWeeds - class="num">1].f);
for (class="type">int f = class="num">1; f < numberWeeds; f++)
{
  if (f != numberWeeds - class="num">1)
  {
    wf [f].start = wf [f - class="num">1].end;
    wf [f].end   = wf [f].start + (weeds [f].f - weeds [numberWeeds - class="num">1].f);
  }
  else
  {
    wf [f].start = wf [f - class="num">1].end;
    wf [f].end   = wf [f].start + (weeds [f - class="num">1].f - weeds [f].f) * class="num">0.1;
  }
}
class="type">bool seedingLimit = class="kw">false;
class="type">int  weedsPos = class="num">0;
for (class="type">int s = pos; s < numberSeeds; s++)
{
  r = RNDfromCI(wf [class="num">0].start, wf [numberWeeds - class="num">1].end);
  for (class="type">int f = class="num">0; f < numberWeeds; f++)
  {
    if (wf [f].start <= r && r < wf [f].end)
    {       
weedsPos = f;
      break;
    }
  }
  if (weeds [weedsPos].s >= maxNumberSeeds)
  {
    seedingLimit = class="kw">false;
    while (!seedingLimit)
    {
      weedsPos++;
      if (weedsPos >= numberWeeds)
      {
        weedsPos = class="num">0;
        seedingLimit = true;
      }
      else
      {
        if (weeds [weedsPos].s < maxNumberSeeds)
        {
          seedingLimit = true;
        }
      }
    }
  }
  for (class="type">int c = class="num">0; c < coordinates; c++)
  {
    r = RNDfromCI(-class="num">1.0, class="num">1.0);
    r = r * r * r;
    seeds [s].c [c] = weeds [weedsPos].c [c] + r * vec [c] * dispersion;
    seeds [s].c [c] = SeInDiSp(seeds [s].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
  seeds [s].s = class="num">0;
  weeds [weedsPos].s++;
}

◍ 种子并入杂草群后的适应度刷新

这段 C_AO_IWO::Germination 做的是把当前迭代产生的种子直接挂到杂草数组尾部,再重排并刷新全局最优。 for (int s = 0; s < numberSeeds; s++) 遍历所有种子,weeds[numberWeeds + s] = seeds[s] 把第 s 个种子写进杂草群下标 numberWeeds+s 的位置,等价于群规模瞬时扩张 numberSeeds 个个体。 Sorting() 之后调用 weeds[0].f > fB 判断:若排完序后最优杂草的适应度超过已记录最优 fB,就更新 fB。注意这里只比较 weeds[0],说明 Sorting 按适应度降序,且 fB 只增不减。 在 MT5 里跑这套时,可打印每次 Germination 后的 numberWeeds 与 fB,验证种子是否真的拉高了全局最优,以及扩张速度是否过快导致内存抖动。

MQL5 / C++
class="type">void C_AO_IWO::Germination()
{
   for (class="type">int s = class="num">0; s < numberSeeds; s++)
   {
      weeds [numberWeeds + s] = seeds [s];
   }
   Sorting();
   if (weeds [class="num">0].f > fB) fB = weeds [class="num">0].f;
}

IWO 在基准函数上的实测表现

在 EURUSD M1 回测台上跑通了入侵杂草优化(IWO)的对照测试,每个测试函数各跑 10000 次。Rastrigin 平滑函数上,5 个实例得分 0.98775、25 个实例 0.82525、500 个实例掉到 0.56277;Forest 离散函数对应得分仅 0.73701 / 0.31848 / 0.06269;Megacity 离散函数则是 0.55333 / 0.21667 / 0.04723。数据摆出来就很直白:IWO 对平滑函数的适应力明显强过离散类,参数规模从 10 涨到 1000 时,离散函数得分衰减更快。 把九项子测试汇总成相对评分(最好 1.0、最差 0.0),IWO 拿下了 Rastrigin 10 参数与 50 参数两项第一,Forest 和 Megacity 各段也基本好于平均,最终结果 100 分列首;修订蚁群 ACOm 以 95.996 分排第二,靠的是 Forest 与 Megacity 上五项最佳。这种评分方式类似显卡跑分排行,方便横向挑算法。 IWO 没有引用种群最优个体,也没设跳离局部极值的机制,却仍在 Forest 和 Megacity 上发挥了作用,尤其是 Megacity 千变量测试里可扩展性仅次于萤火虫。种子离散随迭代线性降低,使末尾极值细化增强,但探索分布不均,收敛图右侧能看到恒定白噪声,锐化启动后多数段落变慢,接近收尾才明显加速。 外汇与贵金属优化属高风险实验,回测高分不等于实盘稳健。IWO 参数虽多但语义直观,微调主要撼动离散函数结果,平滑函数表现依旧硬挺;若想验证,可下载文末 ZIP 在 MT5 策略测试器以相同配置重跑一遍。

「画得少,看得清」

默里系统的核心不在堆满线条,而是用少数关键层级把价格位置说清楚;蝙蝠算法这类种群优化方法,价值也在把平滑问题收敛到能用的参数,而不是秀复杂度。 把这两件工具接进 MT5,你真正要做的不是多画,而是只留当前价格正在触碰的那几条线,其余交给小布这类 AIGC 辅助去跑回测与信号过滤。 外汇与贵金属波动剧烈、杠杆高风险大,任何层级或算法输出都只是概率倾向,实盘前请用策略测试器先验证你那套默里+BA 组合在历史数据上的表现。

常见问题

初始化种群一般取 20~50 株杂草,太少容易陷局部最优,太多拖慢收敛;先用 30 株跑基准函数看散布是否均匀。
算法按个体适应度比例分配种子数,优株后代更集中,加速向高适应区聚集;实作中用归一化适应度乘最大种子数即可。
小布可内置 IWO 流程,你贴基准函数和维度,它自动初始化、播种并刷新适应度,把每代最优曲线直接画给你看。
要重算;新杂草并入群体后必须刷新全群适应度,否则选择压会失真,收敛轨迹可能断掉。
30 维 Sphere 实测约 200~400 代进入 1e-6 精度;若 500 代还没下破,优先查播种方差和最大迭代设置。