原子轨道搜索(AOS)算法(基础篇)
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 代看层分布散度。
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 里能复现撒点形状。
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">//The lognormal distribution of the species: 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>) { <span class="comment">class=class="str">"cmt">// Check the right border</span> <span class="keyword">if</span> (min_value >= max_value) { <span class="keyword">class="kw">return</span> max_value; } <span class="comment">class=class="str">"cmt">// Check the left border</span> <span class="keyword">if</span> (max_value <= min_value) { <span class="keyword">class="kw">return</span> min_value; } <span class="comment">class=class="str">"cmt">// Generate a random number from class="num">0 to class="num">1</span> <span class="keyword">class="type">class="kw">double</span> random = <span class="functions">MathRand</span> () / <span class="number">class="num">32767.0</span>; <span class="comment">class=class="str">"cmt">// Correction of the center if it goes beyond the boundaries</span> <span class="keyword">if</span> (center < min_value) { center = min_value; random = <span class="number">class="num">1</span>; } <span class="keyword">if</span> (center > max_value) { center = max_value; random = <span class="number">class="num">0</span>; } <span class="comment">class=class="str">"cmt">// Calculate the position of the peaks</span> <span class="keyword">class="type">class="kw">double</span> peak_left = center - (center - min_value) * peakDisplCoeff; <span class="keyword">class="type">class="kw">double</span> peak_right = center + (max_value - center) * peakDisplCoeff; <span class="keyword">class="type">class="kw">double</span> result = <span class="number">class="num">0.0</span>; <span class="keyword">if</span> (random < <span class="number">class="num">0.5</span>) <span class="comment">class=class="str">"cmt">// Left side of the distribution</span> { <span class="comment">class=class="str">"cmt">// Calculate parameters for the left side</span> <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>); <span class="keyword">class="type">class="kw">double</span> diff_center_min = <span class="functions">MathMax</span> (center - min_value, <span class="macro">DBL_EPSILON</span>); <span class="keyword">class="type">class="kw">double</span> mu_left = <span class="functions">MathLog</span> (diff_center_peak); <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>); <span class="comment">class=class="str">"cmt">// Generate random numbers for the Box-Muller method</span> <span class="keyword">class="type">class="kw">double</span> u1 = <span class="functions">MathRand</span> () / <span class="number">class="num">32767.0</span>; <span class="keyword">class="type">class="kw">double</span> u2 = <span class="functions">MathRand</span> () / <span class="number">class="num">32767.0</span>; <span class="comment">class=class="str">"cmt">// Protection against null values</span> u1 = <span class="functions">MathMax</span> (u1, <span class="macro">DBL_EPSILON</span>); <span class="comment">class=class="str">"cmt">// Application of the Box-Muller method</span> <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); <span class="comment">class=class="str">"cmt">// Calculate the result for the left side</span> result = center - <span class="functions">MathExp</span> (mu_left + sigma_left * z); } <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 跑一遍,能直接看到右尾比左尾更瘦的抽样形态。
{
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