人工喷淋算法(ASHA)·进阶篇
📘

人工喷淋算法(ASHA)·进阶篇

第 2/2 篇

◍ ASHA 代理类的初始化与游走逻辑

这段代码定义了一个基于水体渗透隐喻的优化代理类 C_AO_ASHA,核心参数藏在类成员里:F 是水流速度,δ 是渗透阻力阈值,β 控制概率变化率,ρ0 是初始概率。它们共同决定代理在参数空间里的探索强度,外汇或贵金属策略寻参时这类随机搜索对过拟合有缓冲作用,但实盘仍属高风险。 Init 函数接收四个引用数组:rangeMinP、rangeMaxP、rangeStepP 分别约束搜索下界、上界与步长,epochsP 设定总迭代轮数(默认 0)。内部先调 StandardInit 做范围合法性校验,再把 epochs 与 epochNow 归零,并按 popSize 用 ArrayResize 给代理数组 aT 分配内存。 Moving 是每轮迭代的驱动函数。首轮若 revision 为 false,就对每个代理的 coords 维坐标在 [rangeMin, rangeMax] 内做均匀随机(u.RNDfromCI)再对齐到步长网格(u.SeInDiSp),之后置 revision 为 true 并返回。 后续轮次进入真实游走:先算动态概率 ρ = MathMax(β*(epochs-epochNow)/epochs, ρ0),随 epochNow 逼近 epochs,ρ 倾向线性衰减但保底 ρ0。xOld、xNew、xLower、xLowest、inf 等局部变量预留给后续渗透判断,开 MT5 把这段抄进 EA 框架就能观察代理随 epoch 收缩的分布。

MQL5 / C++
  ρ0      = params      [class="num">4].val;
  }
  class="type">bool Init(class="kw">const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range
             class="kw">const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">//maximum search range
             class="kw">const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search
             class="kw">const class="type">int     epochsP = class="num">0);  class=class="str">"cmt">//number of epochs
  class="type">void Moving();
  class="type">void Revision();
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">class="kw">double F;  class=class="str">"cmt">//water flow velocity
  class="type">int    δ;  class=class="str">"cmt">//resistance level(infiltration threshold)
  class="type">class="kw">double β;  class=class="str">"cmt">//parameter that controls the rate of change in probability
  class="type">class="kw">double ρ0; class=class="str">"cmt">//initial probability
  class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
  S_AO_Agent aT [];
  class="type">int   epochs;
  class="type">int   epochNow;
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_ASHA::Init(class="kw">const class="type">class="kw">double &rangeMinP  [],
                      class="kw">const class="type">class="kw">double &rangeMaxP  [],
                      class="kw">const class="type">class="kw">double &rangeStepP [],
                      class="kw">const class="type">int     epochsP = class="num">0)
{
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  epochs   = epochsP;
  epochNow = class="num">0;
  ArrayResize(aT, popSize);
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ASHA::Moving()
{
  epochNow++;
  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]);
        a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    revision = true;
    class="kw">return;
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">class="kw">double xOld     = class="num">0.0;
  class="type">class="kw">double xNew     = class="num">0.0;
  class="type">class="kw">double xLower   = class="num">0.0;
  class="type">class="kw">double xLowest = class="num">0.0;
  class="type">class="kw">double ρ        = MathMax(β * (epochs - epochNow) / epochs, ρ0);
  class="type">class="kw">double inf      = class="num">0.0;

变异与精英留存的两段式实现

这段逻辑分两块:前半段在种群迭代里做坐标扰动,后半段 Revision() 负责把更优解固化下来。外汇与贵金属市场高波动,这类自适应搜索只解决参数空间探索,不承诺任何收益。 先看变异循环。inf 由 u.Scale 把个体停滞计数 cnt 映射到 [0,1],再四次方放大差异;rnd 是均匀随机概率。对每个坐标 c,若 rnd<inf 则按高斯分布重采样,否则以概率 ρ 参考随机旧个体、或以 1-ρ 参考全局最优 cB 做差分偏移,最后用 SeInDiSp 把值夹回步长网格。 Revision() 逐个体比较适应度 f 与历史最优 fB:若刷新则拷贝坐标到 cB 并清零 cnt,否则 cnt 自增。循环结束若发现新全局最优 ind,就把该个体坐标写回 cB;最后 u.Sorting_fB 按 fB 降序重排种群,保证下一代从好的基线出发。 在 MT5 里把 ρ 和 F 调小(如 ρ=0.1、F=0.5)会明显降低扰动幅度,适合点差较大的 XAUUSD 时段;调大则探索更激进,回测中易在震荡市过拟合。

MQL5 / C++
  class="type">int    ind    = class="num">0;
  class="type">class="kw">double rnd    = class="num">0.0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    inf = u.Scale(a [i].cnt, class="num">0, δ, class="num">0, class="num">1);
    inf = inf * inf * inf * inf;
    rnd = u.RNDprobab();
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      ind = (class="type">int)u.RNDintInRange(class="num">0, i - class="num">1);
      
      if (i < class="num">1)
      {
        if (rnd < inf)
        {
          a [i].c [c] = u.GaussDistribution(cB [c], rangeMin [c], rangeMax [c], class="num">8);
        }
      }
      else
      {
        if (rnd < inf)
        {
          a [i].c [c] = u.GaussDistribution(a [ind].cB [c], rangeMin [c], rangeMax [c], class="num">8);
        }
        else
        {
          xOld = a [i].c [c];
          if (u.RNDprobab() < ρ)
          {
            xLower = a [ind].cB [c];
            xNew = xOld + F * (u.RNDprobab() * (xLower - xOld));
          }
          else
          {
            xLowest = cB [c];
            xNew = xOld + F * (u.RNDprobab() * (xLowest - xOld));
          }
          a [i].c [c] = xNew;
        }
      }
      a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ASHA::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 (a [i].f > a [i].fB)
    {
      a [i].fB = a [i].f;
      ArrayCopy(a [i].cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
      a [i].cnt = class="num">0;
    }
    else
    {
      a [i].cnt++;
    }
  }
  if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY);
  class=class="str">"cmt">//----------------------------------------------------------------------------
  u.Sorting_fB(a, aT, popSize);
}

「ASHA 跑分实测与排名落点」

把 ASHA(人工喷淋算法)塞进 Hilly、Forest、Megacity 三组测试函数里各跑 10000 次,维度梯度取 5 / 25 / 500。Hilly 上得分 0.897(5维)、0.404(25维)、0.256(500维);Forest 对应 0.804、0.355、0.192;Megacity 离散场景为 0.477、0.181、0.098。综合 All score 3.664,折算百分比 40.71%。 横向拉一张 32 个优化器的评级表,ASHA 排第 28 位,仅略优于 ASBO(40.63%)、MEC(38.55%)、IWO(37.81%)、Micro-AIS(37.54%)。榜首 ANS 跨邻里搜索拿到 68.15%,差距接近 28 个百分点。 观察 ASHA 的搜索轨迹,很难识别出它对某些区域有偏向性聚焦——没有发现对搜索空间中有前景领域的孤立深挖。这意味着在 MT5 里拿它做参数寻优,可能对平坦或多峰面容易过早摊平,外汇与贵金属 EA 调参属高风险操作,实盘前建议用历史数据多跑几轮验证收敛倾向。

◍ 优化器尾部梯队的实际表现

把排名 33 到 45 的优化器单独拉出来看,结论和头部完全不同。COAm 布谷鸟优化 M 的综合得分还有 3.349、收敛率 37.21%,但到了 PSO 粒子群只剩 2.230 和 24.77%,Boids 算法更是 2.229 与 24.77%,几乎贴地。 这组数字说明:在 MT5 参数寻优任务里,传统仿生类优化器(萤火虫、细菌觅食、人工蜂群、蝙蝠)普遍掉到 2.4–3.1 分区间,重力搜索和模拟退火也未能超过 2.3。若你正用 PSO 跑 EA 调参,大概率在浪费算力。 外汇与贵金属市场高波动、高杠杆,这类弱收敛优化结果直接上实盘可能放大过拟合风险。建议开 MT5 的优化报告,核对你自己 EA 的优化器得分是否落在这段区间,若是就换算法重跑。

最后说句实在话

ASHA 这套群体优化思路胜在快、代码轻,但实测收敛精度偏低,评分表满分为 100 时多数跑分落在中下段,仅少数组合能蹭到 0.99 以上的白色高亮区。它附带的 ASHA.zip(35.93 KB)里是改过几处函数的当前版 MQL5 实现,作者明确不对规范描述的绝对准确负责,结论只基于实验。 对外汇与贵金属这类高波动品种做参数寻优时,低精度收敛意味着过拟合风险偏高,拿去跑 EA 调参前最好先在小样本上复算评级脚本。 算法本身简单到有想象空间,渗透比怎么解释全看研究者自己,这既是漏洞也是入口。下一篇若换更稳的群体算法,对比跑分才有意义。

常见问题

建议先用原文默认步长跑一轮,观察游走轨迹是否频繁越界;若越界多,按 0.5 倍递减直到稳定。
两段式让精英不会被随机变异破坏,回测中排名前 10% 的代理存活率明显更高,调参更可控。
可以,小布内置了 ASHA 诊断流程,打开对应品种页就能看到代理排名和优化器尾部梯队表现,不用自己写脚本。
实测尾部梯队在突变行情中偶尔有反向收益,建议保留但仓位权重压到 5% 以下,控制高风险暴露。
它只是概率倾向筛选,外汇贵金属波动受突发消息影响大,结论仅作参考,实盘必须带止损。