原子轨道搜索(AOS)算法:改进与拓展·进阶篇
⚛️

原子轨道搜索(AOS)算法:改进与拓展·进阶篇

(2/3)· 从平均位置到个体最优,拆解 AOS 核心算子的三处提速改造

偏理论进阶 第 2/3 篇
不少人在复现 AOS 时直接套用原始论文的 BSk 平均键,却没意识到它拖慢了随机数生成还弱化了个体收敛。把层平均换成电子个体最优,往往能让复杂解空间的探索更贴物理直觉。这一篇就接着把这套改造讲透。

「原子优化里电子与粒子的位置更新逻辑」

这段 C_AO_AOSm::UpdateElectrons 负责更新电子位置。每个粒子逐坐标处理:先抽转移概率 φ = u.RNDprobab(),若 φ < PR 则直接跳到中心 cB[c],否则按当前能量与层平均能量 BEk 的关系决定朝全局最优还是层内局部最优移动。 移动公式统一为 newPos = a[p].cB[c] + α * (LE - a[p].cB[c]),其中 α 由 u.RNDfromCI(-1.0, 1.0) 在 [-1,1] 随机取,LE 在能量占优时取 cB[c]、否则取 atoms[c].layers[lID].LEk。最后用 u.SeInDiSp 按 rangeMin/Max/Step 把 newPos 夹回离散搜索域。 粒子侧 UpdateParticles 逻辑类似但多一层:当 a[p].f < atoms[c].layers[lID].BEk 时朝全局最优走,公式为 a[p].c[c] + α*(β*LE - γ*BS[c]) / currentLayers[c];否则用层局部量 BSk 替代 BS[c],除以层数的设计让深层搜索步长自然收缩。 开 MT5 把 PR 从默认 0.5 调到 0.2,电子会更少随机跳中心、更倾向沿 LE 收敛,回测曲线可能更快平稳但易陷局部最优;外汇与贵金属品种波动大,该算法调参属高风险实验,建议先用历史数据验证。

MQL5 / C++
class="type">void C_AO_AOSm::UpdateElectrons()
{
  class="type">class="kw">double α;      class=class="str">"cmt">// speed coefficient
  class="type">class="kw">double φ;      class=class="str">"cmt">// transition probability
  class="type">class="kw">double newPos; class=class="str">"cmt">// new position
  class="type">class="kw">double LE;     class=class="str">"cmt">// best energy
  class="type">class="kw">double BSk;    class=class="str">"cmt">// connection state
  class="type">int    lID;    class=class="str">"cmt">// layer ID
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      φ = u.RNDprobab();
      if (φ < PR)
      {
        newPos = cB [c];
      }
      else
      {
        lID = electrons [p].layerID [c];
        α = u.RNDfromCI(-class="num">1.0, class="num">1.0);
        if (a [p].f < atoms [c].layers [lID].BEk)
        {
          LE     = cB [c];
          newPos = a [p].cB [c]+ α * (LE - a [p].cB [c]);
        }
        else
        {
          LE     = atoms [c].layers [lID].LEk;
          newPos = a [p].cB [c]+ α * (LE - a [p].cB [c]);
        }
      }
      a [p].c [c] = u.SeInDiSp(newPos, rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}

◍ 粒子初始化里的两种分布选型

在群体优化算法的粒子初始化阶段,坐标维度的落点分布直接决定早期搜索的覆盖形态。上面这段实现里,同一套循环结构用了两套不同分布:一套走对数正态,一套走高斯。 对数正态那一支调用 LognormalDistribution,传入当前坐标基准 cB[c]、区间上下限与 peakPosition,倾向于把粒子挤在峰值附近但右尾拉长,适合参数空间存在尺度悬殊的场景。高斯那一支写死第 4 个参数为 8,即 u.GaussDistribution(cB[c], rangeMin[c], rangeMax[c], 8),相当于用较宽的标准差把粒子铺开,早期勘探更散。 两个分支都紧接着 SeInDiSp 做离散化对齐,把连续采样值夹回 [rangeMin, rangeMax] 并按 rangeStep 吸附。在 MT5 里把高斯调用的常数 8 改成 2 或 16,能直观看到种群在首代就偏聚集或偏分散,外汇与贵金属参数寻优属高风险实验,结果仅作概率性参考。

MQL5 / C++
for (class="type">int c = class="num">0; c < coords; c++)
{
  class=class="str">"cmt">// Use log-normal distribution to position particles
  a [i].c [c] = u.LognormalDistribution(cB [c], rangeMin [c], rangeMax [c], peakPosition);
  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=class="str">"cmt">// Distribution of particles in the search space
class="type">void C_AO_AOSm::DistributeParticles()
{
  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">// Use a Gaussian distribution to position particles
      a [i].c [c] = u.GaussDistribution(cB [c], rangeMin [c], rangeMax [c], class="num">8);
      a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}

用统一接口跑一次参数优化

所有群体优化算法都从同一个 C_AO 基类派生,所以换算法不需要重写优化逻辑,只要切换枚举就能换引擎。下面这段脚本把抽象接口的用法摊开给你看,复制到 MT5 脚本目录就能跑。 脚本开头用 input 枚举选算法,默认是 NONE_AO;如果不改直接跑,后面会判空并停掉。参数规模写死在代码里:函数总调用次数 10000,优化参数维度 1000,群体大小 50,迭代次数由此算得 200 轮。 每个参数的边界都设在 -10 到 10 之间,步长用 DBL_EPSILON,相当于连续空间里逼近精度极限。目标函数本身是一个倒置抛物线,求的是全局最大值,这种形状没有局部陷阱,适合先验证算法管道通不通。 别把正态当圣经:群体算法在 1000 维里找极值,消耗的函数调用次数和维度成正比,实盘调 EA 参数时先把维度压到 20 以内再谈效率。外汇和贵金属杠杆高,参数过拟合会在极端行情放大回撤,任何优化结果都只是概率倾向。

MQL5 / C++
class="macro">#class="kw">property script_show_inputs                                                                       class=class="str">"cmt">// Specify that the script will show the inputs in the properties window
class="macro">#include <Math\AOs\PopulationAO\class="macro">#C_AO_enum.mqh>                                                      class=class="str">"cmt">// Connect the library for handling optimization algorithms
input E_AO AOexactly = NONE_AO;                                                                     class=class="str">"cmt">// Parameter for selecting the optimization algorithm, class="kw">default is NONE_AO
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void OnStart()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">int numbTestFuncRuns = class="num">10000;                                                                     class=class="str">"cmt">// Total number of function runs
  class="type">int params           = class="num">1000;                                                                      class=class="str">"cmt">// Number of parameters for optimization
  class="type">int popSize          = class="num">50;                                                                        class=class="str">"cmt">// Population size for optimization algorithm
  class="type">int epochCount       = numbTestFuncRuns / popSize;                                                class=class="str">"cmt">// Total number of epochs(iterations) for optimization


  class="type">class="kw">double rangeMin [], rangeMax [], rangeStep [];                                                    class=class="str">"cmt">// Arrays for storing the parameters&class="macro">#x27; boundaries and steps

  ArrayResize(rangeMin,  params);                                                                  class=class="str">"cmt">// Resize &class="macro">#x27;min&class="macro">#x27; borders array
  ArrayResize(rangeMax,  params);                                                                  class=class="str">"cmt">// Resize &class="macro">#x27;max&class="macro">#x27; borders array
  ArrayResize(rangeStep, params);                                                                  class=class="str">"cmt">// Resize the steps array

  class=class="str">"cmt">// Initialize the borders and steps for each parameter
  for (class="type">int i = class="num">0; i < params; i++)
  {
    rangeMin  [i] = -class="num">10;                                                                            class=class="str">"cmt">// Minimum value of the parameter
    rangeMax  [i] =  class="num">10;                                                                            class=class="str">"cmt">// Maximum value of the parameter
    rangeStep [i] =  DBL_EPSILON;                                                                   class=class="str">"cmt">// Parameter step
  }

  class=class="str">"cmt">//----------------------------------------------------------------------------
  C_AO *ao = SelectAO(AOexactly);                                                                  class=class="str">"cmt">// Select an optimization algorithm
  if (ao == NULL)                                                                                   class=class="str">"cmt">// Check if an algorithm has been selected
  {

「把目标函数接进优化器主循环」

上面这段是优化器跑完初始化后真正干活的部分:先按 epochCount 跑主循环,每个 epoch 里调用 Moving() 推进种群,再对种群里每个解算目标函数并写回 ao.a[set].f,最后 Revision() 根据适应度更新种群。 主循环结束后直接 Print 出算法名、最优值 ao.fB 和函数调用总次数 numbTestFuncRuns,随后 delete ao 释放对象。注意如果你没选算法,开头会 Print("AO not selected...") 并 return,后面全不执行。 目标函数这里用了一个示例抛物面:维度不限,每个 x[i] 超出 [-10.0, 10.0] 直接返回 0.0;在范围内则累加 (-x[i]^2 + 100.0) * 0.01 再除以维数,值域落在 [0.0, 1.0],属于最大化问题。你在 MT5 里把 ObjectiveFunction 换成自己的回测收益函数,就能驱动这套种群优化找参数。 外汇与贵金属品种波动大、点差滑点会吃掉优化结果,实盘前务必用历史数据多跑几轮验证,参数最优只代表过去样本,未来可能失效。

MQL5 / C++
  Print("AO not selected...");                                      class=class="str">"cmt">// Error message if no algorithm is selected
  class="kw">return;
  }

  ao.params [class="num">0].val = popSize;                                        class=class="str">"cmt">// Assigning population size....
  ao.SetParams();                                                    class=class="str">"cmt">//... (optional, then class="kw">default population size will be used)


  ao.Init(rangeMin, rangeMax, rangeStep, epochCount); class=class="str">"cmt">// Initialize the algorithm with given boundaries and number of epochs

  class=class="str">"cmt">// Main loop by number of epochs
  for (class="type">int epochCNT = class="num">1; epochCNT <= epochCount; epochCNT++)
  {
    ao.Moving();                                                     class=class="str">"cmt">// Execute one epoch of the optimization algorithm
    class=class="str">"cmt">// Calculate the value of the objective function for each solution in the population
    for (class="type">int set = class="num">0; set < ArraySize(ao.a); set++)
    {
      ao.a [set].f = ObjectiveFunction(ao.a [set].c); class=class="str">"cmt">// Apply the objective function to each solution
    }
    ao.Revision();                                                   class=class="str">"cmt">// Update the population based on the results of the objective function
  }

  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Output the algorithm name, best result and number of function runs
  Print(ao.GetName(), ", best result: ", ao.fB, ", number of function launches: ", numbTestFuncRuns);

  class="kw">delete ao;                                                          class=class="str">"cmt">// Release the memory occupied by the algorithm object
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Definition of the user&class="macro">#x27;s objective function, in this case as an example - a paraboloid, F(Xn) ∈ [class="num">0.0; class="num">1.0], X ∈ [-class="num">10.0; class="num">10.0], maximization
class="type">class="kw">double ObjectiveFunction(class="type">class="kw">double &x [])
{
  class="type">class="kw">double sum = class="num">0.0; class=class="str">"cmt">// Variable for accumulation of the result
  class=class="str">"cmt">// Loop through all parameters
  for (class="type">int i = class="num">0; i < ArraySize(x); i++)
  {
    class=class="str">"cmt">// Check if the parameter is in the allowed range
    if (x [i] < -class="num">10.0 || x [i] > class="num">10.0) class="kw">return class="num">0.0;     class=class="str">"cmt">// If the parameter is out of range, class="kw">return class="num">0
    sum += (-x [i] * x [i] + class="num">100.0) * class="num">0.01;            class=class="str">"cmt">// Calculate the value of the objective function
  }

  class="kw">return sum /= ArraySize(x);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
把参数扫描交给小布盯盘
这些诊断与算子对比,小布盯盘的 AIGC 已内置,打开对应品种页即可看到多版本优化曲线的并排视图,你只管判断哪条更值得跟。

常见问题

原始 BSk 用层内电子坐标算术平均引导移动,容易抹平优质个体的局部信号;改用个体最佳后,电子依据自身历史最优修正轨迹,探索更有方向性,且可省去 βi、γi 两个随机因子,随机数生成耗时约降至三分之一。
小布盯盘内置的 AIGC 视图可加载多组优化参数并排显示权益曲线与收敛带,便于你快速比对原版与改进版在外汇贵金属样本上的稳定性,但实盘仍属高风险,结果仅作概率参考。
被移除的是计算层能量与连通性时依赖平均位置的冗余语句,删除后层参数不再绑定坐标均值,降低陷入局部陷阱的概率,同时减少每代运算开销。
改进版更强调个体最优迁移,对解空间粗糙、峰谷稀疏的日线贵金属问题可能收敛更快,但样本外过拟合风险仍在,建议用 walk-forward 分段验证。