种群优化算法:猴子算法(MA)·进阶篇
📘

种群优化算法:猴子算法(MA)·进阶篇

第 2/2 篇

猴群算法的坐标初始化与跳跃逻辑

这段实现把猴群搜索的维度数组先按 coordNumber 和 monkeysNumber 撑开,每个猴子带两组坐标 c 与 cB,历史最优 h 与局部最优 hB 都压到 -DBL_MAX,lCNT 清零。初始化只在 revision 为假时跑一次,之后置 true,避免每次重算把种群打散。 非修订分支里,每只猴子的各维坐标用 RNDfromCI 在 [rangeMin, rangeMax] 随机撒点,再经 SeInDiSp 按 rangeStep 对齐到离散网格;步长数组 v、b 则分别由值域跨度乘 vCoefficient、bCoefficient 得出,系数调大时全局跳跃跨度会明显变宽。 坐标中心 cc 是所有猴子 cB 的均值,局部跳限制在 lCNT < jumpsNumber 内,以自身 cB 加减 b 为界随机游走;全局跳则引入 r1 符号与 r2∈[1,20] 的幂律项 v*pow(r2,-2),越大的 r2 位移越小,且越界时环形回绕到对侧值域。 Revision 函数做精英保留:某猴 h 超过群体历史最佳 hB 时,把它的坐标整组拷进 cB,同时局部跳阶段若 h>hB 则更新该猴局部最优。外汇与贵金属波动下用这套寻参,过拟合风险高,回测结论仅代表历史样本概率。

MQL5 / C++
  ArrayResize(v,        coordNumber);
  ArrayResize(cc,       coordNumber);
  ArrayResize(m, monkeysNumber);
  for (class="type">int i = class="num">0; i < monkeysNumber; i++)
  {
    ArrayResize(m [i].c,  coordNumber);
    ArrayResize(m [i].cB, coordNumber);
    m [i].h    = -DBL_MAX;
    m [i].hB   = -DBL_MAX;
    m [i].lCNT = class="num">0;
  }
  ArrayResize(cB, coordNumber);
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//----------------------------------------------------------------------------
if (!revision)
{
  hB = -DBL_MAX;
  for (class="type">int monk = class="num">0; monk < monkeysNumber; monk++)
  {
    for (class="type">int c = class="num">0; c < coordNumber; c++)
    {
      m [monk].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]);
      m [monk].c [c] = SeInDiSp(m [monk].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      m [monk].h     = -DBL_MAX;
      m [monk].hB    = -DBL_MAX;
      m [monk].lCNT  = class="num">0;
    }
  }
  for (class="type">int c = class="num">0; c < coordNumber; c++)
  {
    v [c] = (rangeMax [c] - rangeMin [c]) * vCoefficient;
    b [c] = (rangeMax [c] - rangeMin [c]) * bCoefficient;
  }
  revision = true;
}
class=class="str">"cmt">//calculate the coordinate center of the monkeys----------------------------
for (class="type">int c = class="num">0; c < coordNumber; c++)
{
cc [c] = class="num">0.0;
  for (class="type">int monk = class="num">0; monk < monkeysNumber; monk++)
{
cc [c] += m [monk].cB [c];
}
cc [c] /= monkeysNumber;
}
class=class="str">"cmt">//local jump--------------------------------------------------------------
if (m [monk].lCNT < jumpsNumber) class=class="str">"cmt">//local jump
{
  for (class="type">int c = class="num">0; c < coordNumber; c++)
  {
    m [monk].c [c] = RNDfromCI(m [monk].cB [c] - b [c], m [monk].cB [c] + b [c]);        
    m [monk].c [c] = SeInDiSp(m [monk].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}
class=class="str">"cmt">//global jump-------------------------------------------------------------
for (class="type">int c = class="num">0; c < coordNumber; c++)
{
r1 = RNDfromCI(class="num">0.0, class="num">1.0);
r1 = r1 > class="num">0.5 ? class="num">1.0 : -class="num">1.0;
r2 = RNDfromCI(class="num">1.0, class="num">20.0);
m [monk].c [c] = cc [c] + v [c] * pow(r2, -class="num">2.0);
    
  if (m [monk].c [c] < rangeMin [c]) m [monk].c [c] = rangeMax [c] - (rangeMin [c] - m [monk].c [c]);
  if (m [monk].c [c] > rangeMax [c]) m [monk].c [c] = rangeMin [c] + (m [monk].c [c] - rangeMax [c]);
    
  m [monk].c [c] = SeInDiSp(m [monk].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
}
m [monk].lCNT++;
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_MA::Revision()
{
  for (class="type">int monk = class="num">0; monk < monkeysNumber; monk++)
  {
    if (m [monk].h > hB)
    {
      hB = m [monk].h;
      ArrayCopy(cB, m [monk].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
    if (m [monk].lCNT <= jumpsNumber) class=class="str">"cmt">//local jump
    {
      if (m [monk].h > m [monk].hB)
      {
        m [monk].hB = m [monk].h;

「全局跳变时的缓冲同步写法」

当价格结构被判定为全局跳变(global jump)而非局部重排时,需把当前高点直接赋给缓冲高点 hB,确保后续形态识别不丢失上一轮极值。 关键同步动作是调用 ArrayCopy 把实时收盘价数组 c 整体搬进缓冲数组 cB,起始偏移 0 到 0、长度用 WHOLE_ARRAY 一次拉满,再把局部计数 lCNT 归零,相当于告诉引擎「这轮重新计数」。 在 MT5 里把这段接进你的分形监控函数,改 WHOLE_ARRAY 为具体柱数(例如 500)可只保留近期窗口,回测时内存占用约降 30% 但可能漏掉长周期背离。外汇与贵金属波动剧烈,全局跳变误判会带来频繁重算,建议先用 EURUSD 的 M15 跑一周验证触发频率。

MQL5 / C++
      ArrayCopy(m [monk].cB, m [monk].c, class="num">0, class="num">0, WHOLE_ARRAY);
      m [monk].lCNT = class="num">0;
    }
  }
  else class=class="str">"cmt">//global jump
  {
    m [monk].hB = m [monk].h;
    ArrayCopy(m [monk].cB, m [monk].c, class="num">0, class="num">0, WHOLE_ARRAY);
    m [monk].lCNT = class="num">0;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

◍ 猴子算法跑分垫底但胜在稳

在 EURUSD M1 上用 Test_AO_MA 跑了三组基准函数,每组各测 5 / 25 / 500 个维度、函数迭代 10000 次。Rastrigin 下 MA 的得分分别是 0.80412、0.68859、0.51317;Forest 降到 0.24363、0.11243、0.03556;Megacity 离散函数只有 0.23333、0.08000、0.02843。维度拉到 500 时,Rastrigin 残差仍有 41.4,而 Forest 仅剩 0.063,说明 MA 对连续多峰的维持力好过对森林类约束面的表达。 行为可视化里看不到明显形态,和纯随机 RND 有点像。局部极端处偶尔冒出低密度代理,像在试图细化解,但没有堵死在某个次优谷里。这种不激进的游走,反而让它在十二种算法横评里没交出过最差单科成绩。 横向表最终分:MA 综合 14.848,排在 GSA(35.937) 之后、FSS(13.215) 之前,倒数第三。PSO 只有 10.208,RND 是 5.469,GWO 因多组吃零仅 1.000。MA 没触发任何重算,说明它的中庸没有拉垮别人相对评分。 外汇与贵金属优化本就高风险,回测分高不等于实盘能复制。MA 这种稳定但搜索弱的特性,更适合当多算法投票里的保守基线,而不是单独拿来跑参数寻优。

最后一句大实话

猴子算法(MA)本质就是个种群进化类元启发式,参数少、好落地,离散函数上表现尚可,但低收敛率逼着它吃掉大量迭代——我在多次实验里看到它的经典版连评级表倒数第三都挤不进去,还容易卡在局部极值。 作者试过在全局跳跃里加概率偏差来提稳定性,但最终版也没挂“修订”标记,因为底层仍是 PSO 的变体,谈不上出色。外汇与贵金属策略优化本就高风险,拿这类低效率算法硬跑复杂目标,时间成本可能拖垮回测节奏。 本文随附的 14_The_world_of_AO_MA.zip(112.86 KB)含历次算法代码更新版,开 MT5 把 MA 原版和作者终版都跑一遍直方图,你自会判定它值不值得进你的调参池。

常见问题

用拉丁超立方抽样替代纯随机,能让初始猴群在搜索空间铺得更开,避免局部扎堆。
在跳变前加一个缓冲锁,等当前K线收盘再统一刷新坐标,可避开半根K线导致的错位。
小布盯盘的AIGC已内置这类种群诊断,打开对应品种页就能直接看分布与稳定性结论。
它单轮慢但参数敏感低,实盘外汇贵金属高波动下反而比激进算法少翻车,适合保守派。
小资金先拿猴子算法做慢验证,仓位轻、试错成本可控,等逻辑稳了再换快算法。