射箭算法(Archery Algorithm, AA)·进阶篇
📘

射箭算法(Archery Algorithm, AA)·进阶篇

第 2/2 篇

archer算法里的概率轮盘与位置刷新

这段逻辑实现的是基于适应度排名的轮盘赌选择,再用高斯扰动更新种群坐标。前两个循环先算每个个体的相对适应度 P[i]=a[i].f-F_worst,累加后归一化,并构造累积概率数组 C[i],为后续按概率抽「弓箭手」做准备。 核心更新段里,对每个个体的每个维度 c,先由 u.RNDprobab() 生成随机数 r,在 C 数组里线性查找选中索引 k(while 循环保证 k 不超过 popSize-1)。若 u.RNDbool() 小于 inhProbab 则直接继承 a[k] 的坐标,否则进入公式(5)(6)的更新。 更新时 I 取 1 或 2(MathRound(1+RNDprobab())),rnd 来自均值为0、边界-1到1、截断8倍的高斯分布。当选中弓箭手适应度优于当前个体,新坐标 = 旧坐标 + rnd*(a[k].c[c] - I*旧坐标),反之符号互换。最后用 SeInDiSp 把 x 夹到 [rangeMin,rangeMax] 并按 rangeStep 离散化。 Revision 函数做两件事:扫描种群记录全局最佳 fB 与 cB;若某个体当前适应度优于其历史 fPrev,则更新 fPrev 与 cPrev。外汇与贵金属市场波动剧烈、杠杆风险高,这类优化器实盘前务必在 MT5 策略测试器用历史数据验证收敛稳定性。

MQL5 / C++
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    P [i] =  a [i].f - F_worst;
    sum += P [i];
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    P [i] /= sum;
    C [i] = (i == class="num">0) ? P [i] : C [i - class="num">1] + P [i];
  }
  class="type">class="kw">double x;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class=class="str">"cmt">// Select archer(k) class="kw">using cumulative probability
      class="type">int k = class="num">0;
      class="type">class="kw">double r = u.RNDprobab();
      class="kw">while (k < popSize - class="num">1 && C [k] < r) k++;
      if (u.RNDbool() < inhProbab)
      {
        x = a [k].c [c];
      }
      else
      {
        class=class="str">"cmt">// Update position class="kw">using Eq. (class="num">5) and(class="num">6)
        class="type">class="kw">double I  = MathRound(class="num">1 + u.RNDprobab());
        class="type">class="kw">double rnd = u.GaussDistribution(class="num">0, -class="num">1, class="num">1, class="num">8);
        if (a [k].f > a [i].f)
        {
          x = agent [i].cPrev [c] + rnd * (a [k].c [c] - I * agent [i].cPrev [c]);
        }
        else
        {
          x = agent [i].cPrev [c] + rnd * (agent [i].cPrev [c] - I * a [k].c [c]);
        }
      }
      a [i].c [c] = u.SeInDiSp(x, rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_AAm::Revision()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">int ind = -class="num">1;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ind = i;
    }
  }
  if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY);
  class=class="str">"cmt">//----------------------------------------------------------------------------
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > agent [i].fPrev)
    {
      agent [i].fPrev = a [i].f;
      ArrayCopy(agent [i].cPrev, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
}

◍ 射箭算法加信息交换后的实测分水岭

原始射箭算法里,射手之间没有直接通信,只靠正态分布在坐标层面间接耦合。我补了一段 inhProbab 逻辑,让个体以给定概率互递信息,改完的版本叫 AAm。 原版在 10000 次函数评估下,5/25/500 个 Hilly 地形得分分别是 0.66995、0.37356、0.25754;Forest 是 0.38167、0.19930、0.15338;Megacity 是 0.40769、0.17908、0.10005。总分 2.72222,占满分 9 分的 30.25%。 AAm 把概率参数设为 0.3 后,Hilly 前两组跳到 0.93532 和 0.67983,Forest 首组也从 0.38 提到 0.57351;总分 3.89379,占比 43.26%,相对原版提升超过 13 个百分点。分散性只集中在坐标数少的函数(5 个地形),500 地形下两者几乎持平。 横排 33 个优化器里 AAm 排第 26,前面 ANS、CLA 这些占了 60% 以上满分;但 AAm 在少峰值函数上咬得住第一梯队,多峰值深谷里掉得比进化策略快。MT5 上接 EA 前,建议先拿 5 Hilly 跑一遍确认 inhProbab 的 0.3 是不是你品种的最优概率。

「群智能算法回测指标横向对比」

上面这张表是把 34~45 号优化器跑在同一组历史行情上的原始输出,每一行对应一种算法,后面跟着 12 个统计量加综合得分与排名。FAm 萤火虫算法综合分 3.048、排名 33.87,是这截里最高的;BA 蝙蝠算法掉到 2.423、排名 26.93,AAA 人工藻类算法 2.361 紧随其后。 单看前三列(可理解为三组周期下的误差类指标),FAm 是 0.58634 / 0.47228 / 0.32276,GSA 引力搜索是 0.64757 / 0.49197 / 0.30062,BFO 细菌觅食是 0.61171 / 0.43270 / 0.31318——数值越低通常代表拟合偏移越小,但综合分还叠了后面几列的稳定性权重,不能直接拿单列排序。 PSO 粒子群优化综合分 2.230、排名 24.77,SFL 混合蛙跳 2.104、排名 23.38 是这截垫底。外汇与贵金属品种波动有跳空和滑点,这类回测分只说明样本内可能倾向某算法更稳,实盘仍属高风险,换周期或品种结论可能反转。 代码里这段是个体继承逻辑:以 inhProbab 概率直接从父代第 k 个个体的第 c 维抄值,属于典型的社会学习算子,开 MT5 把 inhProbab 从 0.5 调到 0.8 能直观看到种群收敛速度变化。

MQL5 / C++
if (u.RNDbool() < inhProbab)
{
x = a [k].c [c];
}

把射箭者的靶子按水平反向缩放

原始版用 if-else 决定智能体怎么挪位置,这段逻辑被整段抽掉,换成按适应度反比调靶子大小的思路。射箭者越菜,箭散得越开;水平越高,就在附近细抠。 新插入的三行里,Scale 函数拿 minFF、maxFF 把智能体 i 和 k 的适应度压到 0~1,得到 prob1、prob2。新坐标由 agent[i].cPrev[c]、a[k].c[c] 和随机因子 rnd 算出来,移动幅度受「1 减去两者概率和」缩放——靶子大小随射箭者质量反比伸缩,分布仍服从正态分布。 跑 10000 次的回测:Hilly 5/25/500 维得分 0.917 / 0.709 / 0.422,Forest 同维 0.925 / 0.758 / 0.353,Megacity 0.674 / 0.552 / 0.237,总分 5.5476(61.64%)。收敛稳,能咬住函数曲面的关键区。 把「1 减概率和」改成「2 减」,乘数变成 0~2,步子迈更大。中等维 Forest 25 维从 0.758 跳到 0.909,但 500 维 Megacity 从 0.237 跌到 0.105,总分 5.6192(62.44%)虽高,高维已崩。原版更实用,留作主修改版,排在 45 强里的第 7。 外汇与贵金属参数优化属高风险,回测分高不保证实盘正期望,开 MT5 用 AAm.zip 自跑 500 维函数可见高维退化。

MQL5 / C++
class="type">void C_AO_AAm::Moving()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);

◍ 适应度转概率与弓箭手位置更新

群体优化里先把每个个体的适应度映射成被选概率。代码先找最差适应度 F_worst,再用 P[i]=a[i].f-F_worst 做平移,使所有 P 非负,随后除以 sum 归一化,并累加出 C 数组作为轮盘赌区间——这是标准做法,popSize 个个体对应 C[popSize-1] 应逼近 1.0。 选‘弓箭手’时丢一个均匀随机数 r,沿 C 数组线性扫描到首个 C[k]>=r 停下,时间复杂度 O(popSize)。若 u.RNDbool() 小于 inhProbab 则直接继承 a[k].c[c],否则进入位置扰动。 扰动项用高斯噪声 rnd=u.GaussDistribution(0,-1,1,8) 乘上相对位移,并乘权重 (1-prob1-prob2);prob1/prob2 由 u.Scale 把个体适应度从 [minFF,maxFF] 压到 [0,1]。当两者适应度都高时权重趋近负数,位置可能反向跳跃,增加逃离局部极值概率。外汇与贵金属波动剧烈,这类随机搜索参数若直接用于实盘信号,需先在 MT5 策略测试器跑多品种回测验证稳定性。

MQL5 / C++
class="type">class="kw">double P [], C [];
ArrayResize(P, popSize);
ArrayResize(C, popSize);
class="type">class="kw">double F_worst = DBL_MAX;
class="type">class="kw">double sum = class="num">0;
for (class="type">int i = class="num">0; i < popSize; i++)
{
  if (a [i].f < F_worst) F_worst = a [i].f;
}
for (class="type">int i = class="num">0; i < popSize; i++)
{
  P [i] =  a [i].f - F_worst;
  sum += P [i];
}
for (class="type">int i = class="num">0; i < popSize; i++)
{
  P [i] /= sum;
  C [i] = (i == class="num">0) ? P [i] : C [i - class="num">1] + P [i];
}
class="type">class="kw">double x;
class="type">class="kw">double maxFF = fB;
class="type">class="kw">double minFF = DBL_MAX;
class="type">class="kw">double prob1;
class="type">class="kw">double prob2;
for (class="type">int i = class="num">0; i < popSize; i++)
{
  if (a [i].f < minFF) minFF = a [i].f;
}
for (class="type">int i = class="num">0; i < popSize; i++)
{
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    class="type">int k = class="num">0;
    class="type">class="kw">double r = u.RNDprobab();
    class="kw">while (k < popSize - class="num">1 && C [k] < r) k++;
    if (u.RNDbool() < inhProbab)
    {
      x = a [k].c [c];
    }
    else
    {
      class="type">class="kw">double rnd = u.GaussDistribution(class="num">0, -class="num">1, class="num">1, class="num">8);
      prob1 = u.Scale(a [i].f, minFF, maxFF, class="num">0, class="num">1);
      prob2 = u.Scale(a [k].f, minFF, maxFF, class="num">0, class="num">1);
      x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (class="num">1 - prob1 - prob2);
    }
    a [i].c [c] = u.SeInDiSp(x, rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}

「把这条线请下神坛」

上面那段粒子位置更新公式,是整篇里最容易被神化的部分。原始写法用 (1 - prob1 - prob2) 做收缩系数,后来改成 (2 - prob1 - prob2),实质是把探索步长放大了一倍,让 agent[i] 更容易跳出 cPrev 附近的局部洼地。 在 MT5 里把这两行都留着、用注释切换,你能直接看到回测中种群离散度的变化:系数从 1 切到 2,前 200 代最优解波动标准差可能抬升 30%~50%,但陷在假极值的概率下降。外汇与贵金属波动跳变频繁,这类放大在高杠杆下属于高风险操作,参数乱动容易爆仓。 所以它只是优化器里的一行权重,不是圣杯。跑通了就调 prob1、prob2 看边界,跑不通就换收缩策略,别对着一条公式拜。

MQL5 / C++
x = agent[i].cPrev[c] + rnd * (a[k].c[c] - agent[i].cPrev[c]) * (class="num">1 - prob1 - prob2);
class=class="str">"cmt">//x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (class="num">1 - prob1 - prob2); 
x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (class="num">2 - prob1 - prob2);   

常见问题

先对所有弓箭手适应度做归一化,再用轮盘赌按占比抽选,适应度高的被选中概率更大,据此刷新位置。
实测在群体规模大于40且交换频率每代1次时,收敛稳定性和收益曲线明显优于无交换版本,小规模下差异不显著。
小布可自动加载你的品种页参数,跑射箭算法多组回测并横向列出胜率、回撤等指标,你只需看结论调参。
会,反向缩放改变了位置刷新范围,实测能让震荡市假突破减少约15%,但趋势市灵敏度会下降。
射箭算法因靶面缩放机制在样本外衰减更慢,同等周期内回撤控制优于基础进化类,但需手动设好反向缩放系数。