原子轨道搜索(AOS)算法(基础篇)
📘

原子轨道搜索(AOS)算法(基础篇)

第 1/3 篇

AOS 算法在 MT5 里的落地形态

原子轨道搜索(AOS)是一类受量子力学启发的启发式优化算法,核心思路是把候选解当作「电子」在势能面上沿轨道迭代寻优。在 MetaTrader 5 环境里,它常被封装成自定义 EA 或指标,用来替代网格搜索做参数寻优。

  • 年 8 月 8 日有开发者在 MT5 社区贴出可运行示例,截至发文节点该帖获 628 次查看、1 条跟评,说明这类冷门优化器在实盘圈渗透率仍低。

想验证它是否比默认优化器省时间,直接开 MT5 的「策略测试器」载入对应 EA,把优化模式切到自定义,跑同一组参数区间对比迭代次数即可。外汇与贵金属品种波动跳空频繁,任何优化结果仅代表历史样本,实盘存在较高风险。

「把原子轨道当成解空间来搜」

  • 年 Mahdi Azizi 提出的原子轨道搜索(AOS),本质是把量子力学里的电子概率云搬进优化问题。电子不再有确定轨迹,而是在原子核外按量子数 n 形成概率云——n 越大、电子层半径越大、能量越高,这个设定直接变成候选解的位置与能量映射。

AOS 让每个候选解扮演电子,用波函数描述其出现概率,靠光子激发在轨道间跳迁来发射或吸收能量。算法按候选解能量更新位置,能量高低又反过来改变它落在某电子层的概率,于是搜索过程既是找最优、也能随解空间变化自适应。 做 MT5 上的参数寻优时,可把这个机制当成替代遗传算法的选项:开一个 EA 优化任务,把待调参数集视为电子层,观察高 n 层(高能量候选)是否更快收敛。外汇与贵金属优化属高风险实验,回测吻合不等于实盘概率占优。

◍ 把原子轨道塞进优化器的代码骨架

AOS 把候选解当成原子核外的电子,初始种群 X 含 m 个解,每个解的能量 Eᵢ 由目标函数算出,能量越低越优。搜索空间被切成 L 个同心电子层,层数用 [1, L] 间的随机整数 n 指定,层内候选解数量 p 不固定,分布靠对数正态 PDF 模拟电子波行为。 外层直径直接绑定坐标的 [min_value, max_value] 边界,原子核中心落在当前全局最优解点——这比原文含糊的「中心暗示」更利于实盘调参。若种群 50 个候选解,每个坐标原子里就挂 50 个电子,按层半径对数正态散开。 位置更新看 ϕ 与光子速度 PR 的关系:ϕ≥PR 时,Eₖᵢ≥BEₖ 走全局搜索 Xₖᵢ[t+1]=Xₖᵢ[t]+αᵢ×(βᵢ×LE−γᵢ×BS)/k,否则局部搜索吸能;ϕ<PR 则加随机增量 rᵢ 做磁扰移动。 非对称对数正态生成是关键,下面这段在给定中心和偏移系数下出数,峰值偏移默认 0.2,越界值用 RNDfromCI 投回边界防止端点堆积: S_Layer / S_Atom / S_Electron 三个结构分别管层计数、原子层数组、电子层 ID;C_AO_AOS 类从 C_AO 继承,popSize 与 maxLayers 决定规模,PR 控跃迁概率。Init 里 atoms 数组按坐标数扩容并每层 Init(maxLayers),electrons 按 popSize 扩容并配 layerID。 Moving 方法在 revision=false 时 u.RNDfromCI 随机撒点并按 step 对齐;revision=true 后按 atomStage 走 DistributeParticles 或层生成→ID更新→层参数→电子位移,atomStage 每轮 +1,超 photonEmissions 归零循环。外汇与贵金属参数优化属高风险,回测过拟合概率偏高,建议 MT5 策略测试器里先跑 30 代看层分布散度。

MQL5 / C++
class="type">class="kw">double LognormalDistribution(class="type">class="kw">double center, class="type">class="kw">double min_value, class="type">class="kw">double max_value, class="type">class="kw">double peakDisplCoeff=class="num">0.2)
  {
   if(min_value>=max_value) class="kw">return max_value;
   if(max_value<=min_value) class="kw">return min_value;
   class="type">class="kw">double rnd=MathRandom();
   if(center<min_value) { center=min_value; rnd=class="num">1.0; }
   if(center>max_value) { center=max_value; rnd=class="num">0.0; }
   class="type">class="kw">double peak_left=center-(max_value-min_value)*peakDisplCoeff;
   class="type">class="kw">double peak_right=center+(max_value-min_value)*peakDisplCoeff;
   if(rnd<class="num">0.5)
     {
      class="type">class="kw">double mu_left=MathLog(peak_left);
      class="type">class="kw">double sigma_left=class="num">0.5;
      class="type">class="kw">double u1=MathRandom(); class="type">class="kw">double u2=MathRandom();
      class="type">class="kw">double z=MathSqrt(-class="num">2*MathLog(u1))*MathCos(class="num">2*M_PI*u2);
      class="type">class="kw">double res=MathExp(mu_left+sigma_left*z);
      if(res<min_value||res>max_value) class="kw">return RNDfromCI(res,min_value,max_value);
      class="kw">return res;
     }
   else
     {
      class="type">class="kw">double mu_right=MathLog(peak_right);
      class="type">class="kw">double sigma_right=class="num">0.5;
      class="type">class="kw">double u1=MathRandom(); class="type">class="kw">double u2=MathRandom();
      class="type">class="kw">double z=MathSqrt(-class="num">2*MathLog(u1))*MathSin(class="num">2*M_PI*u2);
      class="type">class="kw">double res=MathExp(mu_right+sigma_right*z);
      if(res<min_value||res>max_value) class="kw">return RNDfromCI(res,min_value,max_value);
      class="kw">return res;
     }
  }

原子轨道优化里的粒子落位与分层逻辑

C_AO_AOS 类里 GetOrbitBandID 先按中心切分左右轨道带宽度,粒子位置小于中心走左侧带索引、大于中心走右侧、等于中心直接返 0,返回的是该坐标所属的轨道带编号。DistributeParticles 则在对数正态下撒点:外层循环 popSize 个粒子、内层循环 coords 个坐标,用 cB、rangeMin、rangeMax、peakPosition 生成位置后,再经 SeInDiSp 按步长夹回边界。 CalcLayerParams 逐原子扫层,内部三层循环分别过原子 c、该原子的 currentLayers[c] 层、以及种群电子 e;电子落入 c 原子 L 层时,层内粒子计数加一,BEk 能量与 BSk 状态随电子属性刷新,若 a[e].f 大于暂存 energy 就同步更新 LEk。pc 不为零才求层平均能量与状态,最后对全部电子算总体结合态。 UpdateElectrons 给每个粒子坐标先抽随机概率 φ,φ 小于 PR 就随机散射到范围内新位;否则取 lID 层,能量低于层均值则拉向全局最优、否则靠向层内局部最优,末了再用步长限幅。Revision 只做一件事:扫一遍 a[i].f,超过 fB 就改写全局最优并把 bestIndex 坐标拷进 cB。 下面这段工具函数决定了粒子怎么在对数正态下偏向峰值分布。默认 peakDisplCoeff=0.2,也就是峰值落在距中心 20% 区间处,左右半区各用 Box-Muller 抽一个偏移量,MT5 里直接挂到 EA 里能复现撒点形状。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//The lognormal distribution of the species:&nbsp;&nbsp;min|------P---C---P------|max</span>
<span class="keyword">class="type">class="kw">double</span> C_AO_Utilities :: LognormalDistribution(<span class="keyword">class="type">class="kw">double</span> center, <span class="keyword">class="type">class="kw">double</span> min_value, <span class="keyword">class="type">class="kw">double</span> max_value, <span class="keyword">class="type">class="kw">double</span> peakDisplCoeff = <span class="number">class="num">0.2</span>)
{
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Check the right border</span>
&nbsp;&nbsp;<span class="keyword">if</span> (min_value &gt;= max_value)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> max_value;
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Check the left border</span>
&nbsp;&nbsp;<span class="keyword">if</span> (max_value &lt;= min_value)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> min_value;
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Generate a random number from class="num">0 to class="num">1</span>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> random = <span class="functions">MathRand</span> () / <span class="number">class="num">32767.0</span>;
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Correction of the center if it goes beyond the boundaries</span>
&nbsp;&nbsp;<span class="keyword">if</span> (center &lt; min_value)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;center = min_value;
&nbsp;&nbsp;&nbsp;&nbsp;random = <span class="number">class="num">1</span>;
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="keyword">if</span> (center &gt; max_value)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;center = max_value;
&nbsp;&nbsp;&nbsp;&nbsp;random = <span class="number">class="num">0</span>;
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate the position of the peaks</span>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> peak_left&nbsp;&nbsp;= center - (center - min_value) * peakDisplCoeff;
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> peak_right = center + (max_value - center) * peakDisplCoeff;
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> result = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp;<span class="keyword">if</span> (random &lt; <span class="number">class="num">0.5</span>) <span class="comment">class=class="str">"cmt">// Left side of the distribution</span>
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate parameters for the left side</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> diff_center_peak = <span class="functions">MathMax</span> (center - peak_left, <span class="macro">DBL_EPSILON</span>);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> diff_center_min&nbsp;&nbsp;= <span class="functions">MathMax</span> (center - min_value, <span class="macro">DBL_EPSILON</span>);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> mu_left = <span class="functions">MathLog</span> (diff_center_peak);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> sigma_left = <span class="functions">MathSqrt</span> (<span class="number">class="num">2.0</span> * <span class="functions">MathLog</span> (<span class="functions">MathMax</span> (diff_center_min / diff_center_peak, <span class="macro">DBL_EPSILON</span>)) / <span class="number">class="num">9.0</span>);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Generate random numbers for the Box-Muller method</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> u1 = <span class="functions">MathRand</span> () / <span class="number">class="num">32767.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> u2 = <span class="functions">MathRand</span> () / <span class="number">class="num">32767.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Protection against null values</span>
&nbsp;&nbsp;&nbsp;&nbsp;u1 = <span class="functions">MathMax</span> (u1, <span class="macro">DBL_EPSILON</span>);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Application of the Box-Muller method</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> z = <span class="functions">MathSqrt</span> (-<span class="number">class="num">2.0</span> * <span class="functions">MathLog</span> (u1)) * <span class="functions">MathCos</span> (<span class="number">class="num">2.0</span> * <span class="macro">M_PI</span> * u2);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate the result for the left side</span>
&nbsp;&nbsp;&nbsp;&nbsp;result = center - <span class="functions">MathExp</span> (mu_left + sigma_left * z);
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="keyword">else</span> <span class="comment">class=class="str">"cmt">// Right side of the distribution</span>

「用画布把分布抽样结果画出来」

上面那段右边界抽样收尾后,程序在 OnStart 里直接起了一张 750×400 的位图画布,左上角锚在坐标 (5,30),用来把左右两侧的计数分布可视化。 画布名写成 "Test_Probability_Distribution_Canvas",用 CreateBitmapLabel 建标签;若返回失败就 Print 出 GetLastError 并 return,不会让脚本静默崩掉。建完立刻把 OBJPROP_HIDDEN 设 false、OBJPROP_SELECTABLE 设 true,图表上能看见也能点选。 CountL 和 CountR 两个数组按 Size_P 长度 ArrayResize,再 ArrayInitialize 清零,分别存中心左侧、右侧落点频次。外汇与贵金属价格抽样带高杠杆风险,这套分布仅用于回测或参数敏感性验证,实盘概率倾向而非确定。 清画布并描边框那步紧接着上述属性设置,之后才会往数组填数并映射成柱宽——你开 MT5 把 Size_P 改成 200 跑一遍,能直接看到右尾比左尾更瘦的抽样形态。

MQL5 / C++
  {
    class=class="str">"cmt">// Calculate parameters for the right side
    class="type">class="kw">double diff_peak_center = MathMax(peak_right - center, DBL_EPSILON);
    class="type">class="kw">double diff_max_center  = MathMax(max_value - center,  DBL_EPSILON);
    class="type">class="kw">double mu_right     = MathLog(diff_peak_center);
    class="type">class="kw">double sigma_right = MathSqrt(class="num">2.0 * MathLog(MathMax(diff_max_center / diff_peak_center, DBL_EPSILON)) / class="num">9.0);
    class=class="str">"cmt">// Generate random numbers for the Box-Muller method
    class="type">class="kw">double u1 = MathRand() / class="num">32767.0;
    class="type">class="kw">double u2 = MathRand() / class="num">32767.0;
    class=class="str">"cmt">// Protection against null values
    u1 = MathMax(u1, DBL_EPSILON);
    class=class="str">"cmt">// Application of the Box-Muller method
    class="type">class="kw">double z = MathSqrt(-class="num">2.0 * MathLog(u1)) * MathCos(class="num">2.0 * M_PI * u2);
    class=class="str">"cmt">// Calculate the result for the right side
    result = center + MathExp(mu_right + sigma_right * z);
  }
  class=class="str">"cmt">// Check and correct the result if it goes beyond the limits
  if (result < min_value || result > max_value) class="kw">return RNDfromCI(min_value, max_value);
  class="kw">return result;
}
class=class="str">"cmt">// Function called at startup
class="type">void OnStart()
{
    CCanvas Canvas; class=class="str">"cmt">// Object for handling graphics(canvas)
    class=class="str">"cmt">// Canvas parameters
    class="type">int W = class="num">750; class=class="str">"cmt">// Canvas width
    class="type">int H = class="num">400; class=class="str">"cmt">// Canvas height
    class="type">int O = class="num">10;  class=class="str">"cmt">// Margins from canvas borders
    class=class="str">"cmt">// Arrays for storing count values
    class="type">int CountL []; class=class="str">"cmt">// Count values to the left of the class="kw">input
    class="type">int CountR []; class=class="str">"cmt">// Count values to the right of the class="kw">input
    class=class="str">"cmt">// Initialize arrays
    ArrayResize(CountL, Size_P); class=class="str">"cmt">// Resize the CountL array
    ArrayInitialize(CountL, class="num">0);  class=class="str">"cmt">// Initialize the CountL array with zeros
    ArrayResize(CountR, Size_P);  class=class="str">"cmt">// Resize the CountR array
    ArrayInitialize(CountR, class="num">0);  class=class="str">"cmt">// Initialize the CountR array with zeros
    class=class="str">"cmt">// Create a canvas
    class="type">class="kw">string canvasName = "Test_Probability_Distribution_Canvas"; class=class="str">"cmt">// Canvas name
    if (!Canvas.CreateBitmapLabel(canvasName, class="num">5, class="num">30, W, H, COLOR_FORMAT_ARGB_RAW))
    {
        Print("Error creating Canvas: ", GetLastError()); class=class="str">"cmt">// Display an error if creation fails
        class="kw">return;
    }
    class=class="str">"cmt">// Set up canvas properties
    ObjectSetInteger(class="num">0, canvasName, OBJPROP_HIDDEN, false);    class=class="str">"cmt">// Make canvas visible
    ObjectSetInteger(class="num">0, canvasName, OBJPROP_SELECTABLE, true); class=class="str">"cmt">// Make canvas selectable
    class=class="str">"cmt">// Clear the canvas and draw the border

常见问题

用来在参数解空间里做全局搜参,替代网格暴力遍历,更快找到较优参数组合,但结果仍属概率性,不保证盈利。
看迭代中最佳适应度连续多代不再明显变化,或抽样分布已覆盖主要区域,即可停搜;外汇贵金属属高风险,需样本外验证。
可以,小布盯盘的 AIGC 已内置此类解空间诊断,打开对应品种页即可看到分布与落位建议,你只需复核决策。
按解空间维度与计算预算分层,常见 3–5 层;层间做信息迁移,避免过度集中导致早熟,具体看回测稳定性。
看聚类中心是否偏离历史优区、离散度是否过大;若呈多峰可能暗示参数平原,贵金属交易务必控仓防风险。