原子轨道搜索(AOS)算法:改进与拓展·进阶篇
(2/3)· 从平均位置到个体最优,拆解 AOS 核心算子的三处提速改造
「原子优化里电子与粒子的位置更新逻辑」
这段 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 收敛,回测曲线可能更快平稳但易陷局部最优;外汇与贵金属品种波动大,该算法调参属高风险实验,建议先用历史数据验证。
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,能直观看到种群在首代就偏聚集或偏分散,外汇与贵金属参数寻优属高风险实验,结果仅作概率性参考。
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 以内再谈效率。外汇和贵金属杠杆高,参数过拟合会在极端行情放大回撤,任何优化结果都只是概率倾向。
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 换成自己的回测收益函数,就能驱动这套种群优化找参数。 外汇与贵金属品种波动大、点差滑点会吃掉优化结果,实盘前务必用历史数据多跑几轮验证,参数最优只代表过去样本,未来可能失效。
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">//——————————————————————————————————————————————————————————————————————————————