原子轨道搜索(AOS)算法:改进与拓展(基础篇)
「原子轨道搜索在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 相关行。
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="keyword">class="kw">struct</span> S_Layer { <span class="keyword">class="type">int</span> pc; <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);"> <span class="keyword">class="type">class="kw">double</span> BSk; <span class="comment">class=class="str">"cmt">// connection state</span></span> <span class="keyword">class="type">class="kw">double</span> BEk; <span class="comment">class=class="str">"cmt">// binding energy</span> <span class="keyword">class="type">class="kw">double</span> LEk; <span class="comment">class=class="str">"cmt">// minimum energy</span> <span class="keyword">class="type">void</span> Init() { pc = <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);"> BSk = <span class="number">class="num">0.0</span>;</span> BEk = <span class="number">class="num">0.0</span>; LEk = <span class="number">class="num">0.0</span>; } }; <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() { <span class="keyword">class="type">class="kw">double</span> energy; <span class="comment">class=class="str">"cmt">// Handle each coordinate(atom)</span> <span class="keyword">for</span> (<span class="keyword">class="type">int</span> c = <span class="number">class="num">0</span>; c < coords; c++) { atoms [c].Init(maxLayers); <span class="comment">class=class="str">"cmt">// Handle each layer</span> <span class="keyword">for</span> (<span class="keyword">class="type">int</span> L = <span class="number">class="num">0</span>; L < currentLayers [c]; L++) { energy = -<span class="macro">DBL_MAX</span>; <span class="comment">class=class="str">"cmt">// Handle each electron</span> <span class="keyword">for</span> (<span class="keyword">class="type">int</span> e = <span class="number">class="num">0</span>; e < popSize; e++) { <span class="keyword">if</span> (electrons [e].layerID [c] == L) { atoms [c].layers [L].pc++; 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);"> atoms [c].layers [L].BSk += a [e].c [c];</span> <span class="keyword">if</span> (a [e].f > energy) { energy = a [e].f; atoms [c].layers [L].LEk = a [e].c [c]; } } } <span class="comment">class=class="str">"cmt">// Calculate average values for the layer</span> <span class="keyword">if</span> (atoms [c].layers [L].pc != <span class="number">class="num">0</span>) { 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);"> atoms [c].layers [L].BSk /= atoms [c].layers [L].pc;</span> } } }
double BSk 存层连接态,Init 中 BSk = 0.0 初始化;CalcLayerParams 内 atoms[c].layers[L].BSk += a[e].c[c] 累加坐标,BSk /= pc 算平均——这三处红块删掉后,层只保留 BEk(能量均值)和 LEk(层内最优坐标)。
原版电子初始化用对数正态分布,仍有非零概率命中空间任一点;改成标准差固定为 8 的正态分布,小标准差偏广域探索,大标准差利全局收敛。外汇与贵金属参数优化属高风险实验,改完请在 MT5 策略测试器以 AOSm 标识跑排名表验证收敛差异。
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="keyword">class="kw">struct</span> S_Layer { <span class="keyword">class="type">int</span> pc; <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);"> <span class="keyword">class="type">class="kw">double</span> BSk; <span class="comment">class=class="str">"cmt">// connection state</span></span> <span class="keyword">class="type">class="kw">double</span> BEk; <span class="comment">class=class="str">"cmt">// binding energy</span> <span class="keyword">class="type">class="kw">double</span> LEk; <span class="comment">class=class="str">"cmt">// minimum energy</span> <span class="keyword">class="type">void</span> Init() { pc = <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);"> BSk = <span class="number">class="num">0.0</span>;</span> BEk = <span class="number">class="num">0.0</span>; LEk = <span class="number">class="num">0.0</span>; } }; <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() { <span class="keyword">class="type">class="kw">double</span> energy; <span class="comment">class=class="str">"cmt">// Handle each coordinate(atom)</span> <span class="keyword">for</span> (<span class="keyword">class="type">int</span> c = <span class="number">class="num">0</span>; c < coords; c++) { atoms [c].Init(maxLayers); <span class="comment">class=class="str">"cmt">// Handle each layer</span> <span class="keyword">for</span> (<span class="keyword">class="type">int</span> L = <span class="number">class="num">0</span>; L < currentLayers [c]; L++) { energy = -<span class="macro">DBL_MAX</span>; <span class="comment">class=class="str">"cmt">// Handle each electron</span> <span class="keyword">for</span> (<span class="keyword">class="type">int</span> e = <span class="number">class="num">0</span>; e < popSize; e++) { <span class="keyword">if</span> (electrons [e].layerID [c] == L) { atoms [c].layers [L].pc++; 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);"> atoms [c].layers [L].BSk += a [e].c [c];</span> <span class="keyword">if</span> (a [e].f > energy) { energy = a [e].f; atoms [c].layers [L].LEk = a [e].c [c]; } } } <span class="comment">class=class="str">"cmt">// Calculate average values for the layer</span> <span class="keyword">if</span> (atoms [c].layers [L].pc != <span class="number">class="num">0</span>) { 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);"> atoms [c].layers [L].BSk /= atoms [c].layers [L].pc;</span> } } }
「原子优化里的连接态与电子更新细节」
种群每次迭代后,先算所有连接维度的群体均值。下面这段把 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] 概率权重,分别拉向全局最优和平均连接态。外汇与贵金属参数寻优用这套逻辑时,过拟合风险高,实盘前务必用历史数据多周期验证。
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];