原子轨道搜索(AOS)算法:改进与拓展(基础篇)
📘

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

第 1/3 篇

「原子轨道搜索在MT5里的落地形态」

原子轨道搜索(AOS)本质是把粒子在原子轨道间的跃迁逻辑搬进优化器,用来替代传统的随机游走或网格遍历。在 MetaTrader 5 的算法交易框架里,它表现为一组可封装的 C++ 类,交易者可以直接挂到 EA 的优化流程中。 这套方法在 2025 年 7 月初由社区开发者 Andrey Dik 放出实测,原文页显示浏览量 475、互动 5,属于早期验证阶段,样本量还不足以下统计结论。 对实战有意义的是:它把「轨道能级」映射成参数空间的距离度量,让优化器倾向跳过明显低效区。外汇与贵金属杠杆品种波动剧烈,任何优化器都只是缩小搜索范围,不等于过滤了过拟合风险。

◍ 从原子轨道模型到可改进的搜索算子

前一篇已经把 AOS(原子轨道搜索)的底层逻辑拆开看过:它借用原子轨道的概率分布和粒子间相互作用,在复杂解空间里做随机但受约束的寻优。核心不是穷举,而是用概率密度引导采样,让搜索倾向于高潜力区域。 这一节要做的不是复述原理,而是动它的算子。AOS 本身已经能跑,但面对多峰、高维的 MT5 参数优化任务时,探索与开发的平衡仍偏保守——实测在 20 维以上问题中后期容易陷在局部密度峰。 改进思路集中在两类特有算子:一是轨道跃迁概率的动态调整,让算法在迭代后期仍保留一定长距离跳跃可能;二是引入自适应相互作用半径,避免粒子过早聚团。目标很直接:把现有 AOS 结构补强,使其在 EA 参数寻优里更扛造。外汇与贵金属市场高波动、高风险,任何优化结果都只是概率优势,需上 MT5 用历史数据复验。

把 AOS 分子模型改成个体导向

原版 AOS 把种群当分子、搜索区域当原子,电子就是具体坐标解。层能量 BEk 取层内电子能量算术平均,BSk 键取坐标平均,位移公式 Xki[t+1] = Xki[t] + αi × (βi × LEk − γi × BSk) 里 βi、γi 两个随机因子纯属冗余,外层已有 αi 做随机扰动。 直接砍掉 BSk,让电子奔着自身历史最优而不是层平均解移动;同时删掉 βi、γi,随机数生成耗时可能压到原来的三分之一,物理含义不丢。下面这段结构体与层参数计算里,红色部分就是该删的 BSk 相关行。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class="kw">struct</span> S_Layer
{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;pc;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// particle counter</span>
<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> BSk; <span class="comment">class=class="str">"cmt">// connection state</span></span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> BEk; <span class="comment">class=class="str">"cmt">// binding energy</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> LEk; <span class="comment">class=class="str">"cmt">// minimum energy</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">void</span> Init()
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc&nbsp;&nbsp;= <span class="number">class="num">0</span>;
<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BSk = <span class="number">class="num">0.0</span>;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BEk = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LEk = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;}
};
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="comment">class=class="str">"cmt">// Calculate parameters for each layer</span>
<span class="keyword">class="type">void</span> C_AO_AOS::CalcLayerParams()
{
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> energy;
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Handle each coordinate(atom)</span>
&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> c = <span class="number">class="num">0</span>; c &lt; coords; c++)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].Init(maxLayers);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Handle each layer</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> L = <span class="number">class="num">0</span>; L &lt; currentLayers [c]; L++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;energy = -<span class="macro">DBL_MAX</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Handle each electron</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> e = <span class="number">class="num">0</span>; e &lt; popSize; e++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (electrons [e].layerID [c] == L)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].pc++;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].BEk += a [e].f;
<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].BSk += a [e].c [c];</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (a [e].f &gt; energy)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;energy = a [e].f;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].LEk = a [e].c [c];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate average values for the layer</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (atoms [c].layers [L].pc != <span class="number">class="num">0</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].BEk /= atoms [c].layers [L].pc;
<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].BSk /= atoms [c].layers [L].pc;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}
逐行拆解:S_Layer 里 double BSk 存层连接态,Init 中 BSk = 0.0 初始化;CalcLayerParams 内 atoms[c].layers[L].BSk += a[e].c[c] 累加坐标,BSk /= pc 算平均——这三处红块删掉后,层只保留 BEk(能量均值)和 LEk(层内最优坐标)。 原版电子初始化用对数正态分布,仍有非零概率命中空间任一点;改成标准差固定为 8 的正态分布,小标准差偏广域探索,大标准差利全局收敛。外汇与贵金属参数优化属高风险实验,改完请在 MT5 策略测试器以 AOSm 标识跑排名表验证收敛差异。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class="kw">struct</span> S_Layer
{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;pc;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// particle counter</span>
<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> BSk; <span class="comment">class=class="str">"cmt">// connection state</span></span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> BEk; <span class="comment">class=class="str">"cmt">// binding energy</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> LEk; <span class="comment">class=class="str">"cmt">// minimum energy</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">void</span> Init()
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc&nbsp;&nbsp;= <span class="number">class="num">0</span>;
<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BSk = <span class="number">class="num">0.0</span>;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BEk = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LEk = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;}
};
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="comment">class=class="str">"cmt">// Calculate parameters for each layer</span>
<span class="keyword">class="type">void</span> C_AO_AOS::CalcLayerParams()
{
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> energy;
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Handle each coordinate(atom)</span>
&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> c = <span class="number">class="num">0</span>; c &lt; coords; c++)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].Init(maxLayers);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Handle each layer</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> L = <span class="number">class="num">0</span>; L &lt; currentLayers [c]; L++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;energy = -<span class="macro">DBL_MAX</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Handle each electron</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> e = <span class="number">class="num">0</span>; e &lt; popSize; e++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (electrons [e].layerID [c] == L)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].pc++;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].BEk += a [e].f;
<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].BSk += a [e].c [c];</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (a [e].f &gt; energy)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;energy = a [e].f;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].LEk = a [e].c [c];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate average values for the layer</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (atoms [c].layers [L].pc != <span class="number">class="num">0</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].BEk /= atoms [c].layers [L].pc;
<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atoms [c].layers [L].BSk /= atoms [c].layers [L].pc;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}

「原子优化里的连接态与电子更新细节」

种群每次迭代后,先算所有连接维度的群体均值。下面这段把 BS 数组清零,再双层循环把每个粒子在该维度的连接值累加,最后除以 popSize 得到平均连接态,数值落在 0~1 之间,反映该维度被群体“激活”的程度。 // Calculate the general state of connections ArrayInitialize (BS, 0); for (int c = 0; c < coords; c++) { for (int e = 0; e < popSize; e++) { BS [c] += a [e].c [c]; } BS [c] /= popSize; } 逐行看:ArrayInitialize 把 BS 全置 0;外层 c 跑每个坐标(维度),内层 e 跑每个粒子,把 a[e].c[c] 累加到 BS[c];出内层后除以 popSize 得均值。MT5 里若 popSize=50、coords=10,BS 就是长度 10 的归一化向量。 Revision 方法负责刷新最优解。先扫一遍种群,若某粒子适应度 a[i].f 大于全局最优 fB 就更新 fB 和 bestIndex;同时若大于自身历史最优 a[i].fB,就把 a[i].fB 更新并 ArrayCopy 存下坐标 cB。最后 bestIndex 有效才把全局最优坐标 cB 抄过来。 电子更新是算法核心。每个粒子每个维度先抽转移概率 φ,若 φ<PR 就直接在参数范围内随机散射;否则按层能量比较走引力更新。α 从 [-1,1] 随机取,β、γ 是 [0,1] 概率权重,分别拉向全局最优和平均连接态。外汇与贵金属参数寻优用这套逻辑时,过拟合风险高,实盘前务必用历史数据多周期验证。

MQL5 / C++
class=class="str">"cmt">// Calculate the general state of connections
ArrayInitialize(BS, class="num">0);
for (class="type">int c = class="num">0; c < coords; c++)
{
  for (class="type">int e = class="num">0; e < popSize; e++)
  {
    BS [c] += a [e].c [c];
  }
  BS [c] /= popSize;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Method of revising the best solutions
class="type">void C_AO_AOSm::Revision()
{
  class="type">int bestIndex = -class="num">1;
  class=class="str">"cmt">// Find the best solution in the current iteration
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class=class="str">"cmt">// Update the global best solution
    if (a [i].f > fB)
    {
      fB = a [i].f;
      bestIndex = i;
    }
    class=class="str">"cmt">// Update the personal best solution
    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);
    }
  }
  class=class="str">"cmt">// Update the best coordinates if a better solution is found 
  if (bestIndex != -class="num">1) ArrayCopy(cB, a [bestIndex].c, class="num">0, class="num">0, WHOLE_ARRAY);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Update electron positions
class="type">void C_AO_AOS::UpdateElectrons()
{
  class="type">class="kw">double α;       class=class="str">"cmt">// speed coefficient
  class="type">class="kw">double β;       class=class="str">"cmt">// best solution weight coefficient
  class="type">class="kw">double γ;       class=class="str">"cmt">// average state weight 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
  class=class="str">"cmt">// Handle each particle
  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)
      {
        class=class="str">"cmt">// Random scatter
        newPos = u.RNDfromCI(rangeMin [c], rangeMax [c]);
      }
      else
      {
        lID = electrons [p].layerID [c];
        α = u.RNDfromCI(-class="num">1.0, class="num">1.0);
        β = u.RNDprobab();
        γ = u.RNDprobab();
        class=class="str">"cmt">// If the current particle energy is less than the average layer energy
        if (a [p].f < atoms [c].layers [lID].BEk)
        {
          class=class="str">"cmt">// Moving towards the global optimum----------------------------
          LE = cB [c];

常见问题

可以,把分子模型改成个体导向后,每个粒子按自身历史最优更新,先用小样本回测验证稳定性再上实盘。
连接态控制个体间信息交换强度,电子更新依赖学习因子;先固定连接态为0.5再调电子步长,避免同时动多个量。
小布可直接调用内置的 AOS 变体做参数寻优与诊断,你只需喂入品种和约束,不用手写搜索算子。
大概率是电子更新没随周期重标定,不同周期噪声尺度不同,建议按周期单独标定学习率再对比。
有可能,个体导向削弱全局探索,可保留少量连接态交叉或加随机扰动来平衡收敛与跳出。