算术优化算法(AOA):从AOA到SOA(简单优化算法)·进阶篇
(2/3)· 明明有四则运算就能搜最优,为什么多数EA还在硬调参数碰运气
「粒子位置更新与离散化约束」
这段逻辑跑在算术优化算法的核心循环里:每个粒子维度 c 按当前最优 best 与随机系数 μ 做偏移,MoPr 控制探索幅度,rangeMax[c] 与 rangeMin[c] 框住可行域。 代码里先判断方向——小于 best 就减、大于就加,再统一用 SeInDiSp 把连续值吸附到离散步长上,避免下发给 EA 的参数出现非法浮点。 a[i].c[c] = u.SeInDiSp(a[i].c[c], rangeMin[c], rangeMax[c], rangeStep[c]); 这一行决定你调参面板里看到的数字是不是「干净」的。若 rangeStep 设 0.0,函数直接透传原值,等于关掉离散化。 Revision 函数负责在每代结束后扫一遍种群:fB 记录历史最佳适应度,ind 存对应的粒子下标,只有 ind != -1 才把 cB 数组覆盖成新最优坐标。popSize 若设 30,最坏情况要比较 30 次 a[i].f > fB,这是你测速时该盯的瓶颈点。 开 MT5 把 rangeStep 从 0.1 改成 0.01 跑同一品种,参数粒度变细后回测曲线可能更平滑,但优化耗时倾向翻倍,黄金等杠杆品种请先确认点差风险。
{
a [i].c [c] = best - (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); class=class="str">"cmt">// Update particle position
}
else
{
a [i].c [c] = best + (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); class=class="str">"cmt">// Update particle position
}
}
a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); class=class="str">"cmt">// Convert to discrete values
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_AOA::Revision()
{
class="type">int ind = -class="num">1; class=class="str">"cmt">// Index to store the best particle
for (class="type">int i = class="num">0; i < popSize; i++) class=class="str">"cmt">// For each particle
{
if (a [i].f > fB) class=class="str">"cmt">// If the function value is better than the current best one
{
fB = a [i].f; class=class="str">"cmt">// Update the best value of the function
ind = i; class=class="str">"cmt">// Save the index of the best particle
}
}
if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY); class=class="str">"cmt">// Copy the coordinates of the best particle
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_Utilities :: SeInDiSp(class="type">class="kw">double In, class="type">class="kw">double InMin, class="type">class="kw">double InMax, class="type">class="kw">double Step)
{
if (In <= InMin) class="kw">return (InMin);
if (In >= InMax) class="kw">return (InMax);
if (Step == class="num">0.0) class="kw">return (In);
else class="kw">return (InMin + Step * (class="type">class="kw">double)MathRound((In - InMin) / Step));
}AOA基准分只有21分,改了五次才破40
算术优化算法(AOA)的原始逻辑在三类测试地形上跑了基准:Hilly、Forest、Megacity,每档又分 5 / 25 / 500 个峰,函数各跑 10000 次。原始版总分 1.90578,折算百分制仅 21.18%,在对比榜里垫底。 先试了在 μ 位移比例乘一个 0~1 随机数,想靠随机性增强搜索多样性。结果总分反而掉到 1.86979(20.78%),比原版更差。接着把四个数学运算符里共用的 rangeMin[c] 项全删掉——它本来让所有表达式都以参数下界为中心,没什么道理。删完总分 1.87959(20.88%),几乎没动。 真正起变化的是引入种群最优解信息交换:用随迭代线性增长的 MoA 组件,按概率把全局最优坐标拷进当前解。这一改总分升到 1.89320(21.04%),但提升很有限。 拐点出现在同一段代码里再加一条:在可接受范围内按 MoP 方程非线性递减的概率生成随机坐标。总分直接跳到 3.67520(40.84%)。关键机制是——迭代初期复制最优解概率极低、全空间随机概率极高,随 epoch 推进两者反向切换,探索→开发的自然过渡。 既然算术运算符的改动一直没显著效果,干脆把四种运算符全去掉,只留最优解复制 + 随机重置。总分再升到 4.08619(45.40%),比原始版高近 24 个百分点。附带好处是少了一个外部参数、跑得更快。外汇与贵金属实盘若套这类优化器做参数寻优,仍属高风险,回测分高不等于 live 能复现。
◍ 从AOA到SOA:低维优化器的逆袭
| 把原始算术优化算法(AOA)的参数 `50.0 | 0.1 | 0.5 | 10.0` 直接跑测试,在 Hilly、Forest、Megacity 三类函数上各做 10000 次采样:Hilly 在 5/25/500 维度得分 0.9152 / 0.4698 / 0.2709,Forest 同维度 0.8967 / 0.3740 / 0.1698,Megacity 为 0.6954 / 0.2803 / 0.1085。原始 AOA 最终总分 4.18(占比 46.45%),排名跌出前 45,基本无缘榜单。 |
|---|
重构后的搜索逻辑彻底抛弃了原算法框架,新版本被命名为简单优化算法(SOA)。它在低维任务上表现异常紧凑——5 维 Hilly 得分 0.9152,结果分布范围明显小于多数对比算法,这种低维稳定性在元启发式里相当少见。 SOA 拿到的榜单成绩是第 29 位,总分 4.181(46.45%),比原 AOA 前进了 16 名以上。横向看,排它前面的 ANS、CLA、AMOm 总分都在 5.98 以上(66%+),而它仅以微弱劣势领先 ABHA(4.127)和 ACMO(4.041)。 别把榜单名次当圣旨 SOA 的优势集中在低维,500 维 Megacity 只剩 0.1085,高频参数优化若维度拉高,收敛质量可能快速衰减。MT5 上做 EA 参数寻优时,先用 5~25 维小空间验证再扩维更稳妥。
「群智能算法在参数寻优上的实跑差距」
把 9 种常见优化器塞进同一套 MT5 指标参数寻优框架后,横向数据很直白:布谷鸟优化(COAm)综合得分 3.349、评级 37.21,排在样本首位;蝙蝠算法(BA)只有 2.423、26.93,人工藻类(AAA)2.361、26.23 垫底。外汇与贵金属 EA 调参属于高风险操作,这些数值仅反映回测环境内的寻优效率,实盘漂移概率不低。 上面那段 MQL5 是研究阶段的除法/乘法算子核心循环,逐行拆一下:外层遍历种群每个粒子,内层遍历每个坐标维度;rand1~rand3 是 [0,1) 均匀随机,best 取当前维度最优值,μ 乘上 0~1 区间随机数做步长扰动。若 rand1 小于 MoAc 则进入乘除分支,rand2>0.5 用除法更新位置、否则用乘法;否则进入加减分支,rand3 决定减或加。最后 SeInDiSp 把连续值夹回离散搜索网格。 直接开 MT5 把 MoAc 从默认往 0.3~0.7 区间扫一遍,同数据下 COAm 与 SDOm 的评级差可能从 0.77 缩小到 0.2 以内,说明控制探索/开发比例比换算法更实在。
class=class="str">"cmt">// Research phase class="kw">using Division(D) and Multiplication(M) operators for (class="type">int i = class="num">0; i < popSize; i++) class=class="str">"cmt">// For each particle { for (class="type">int c = class="num">0; c < coords; c++) class=class="str">"cmt">// For each coordinate { class="type">class="kw">double rand1 = u.RNDprobab(); class=class="str">"cmt">// Generate a random value class="type">class="kw">double rand2 = u.RNDprobab(); class=class="str">"cmt">// Generate a random value class="type">class="kw">double rand3 = u.RNDprobab(); class=class="str">"cmt">// Generate a random value best = cB [c]; class=class="str">"cmt">// Save the current best value μ *= u.RNDfromCI(class="num">0, class="num">1); class=class="str">"cmt">// Random change of μ if (rand1 < MoAc) class=class="str">"cmt">// If random value is less than MoAc { if (rand2 > class="num">0.5) class=class="str">"cmt">// If random value is greater than class="num">0.5 { a [i].c [c] = best / (MoPr + ϵ) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); class=class="str">"cmt">// Update particle position } else { a [i].c [c] = best * (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); class=class="str">"cmt">// Update particle position } } else class=class="str">"cmt">// If random value is greater than or equal to MoAc { if (rand3 > class="num">0.5) class=class="str">"cmt">// If random value is greater than class="num">0.5 { a [i].c [c] = best - (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); class=class="str">"cmt">// Update particle position } else { a [i].c [c] = best + (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ + rangeMin [c]); class=class="str">"cmt">// Update particle position } } a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); class=class="str">"cmt">// Convert to discrete values } }
粒子位置的除法与乘法扰动
这段逻辑跑在探索阶段,对种群里每个粒子、每个坐标维度做位置刷新。先用三个 [0,1) 均匀随机值 rand1~rand3 决定走哪条分支,再拿当前维度历史最优 cB[c] 当锚点。 μ 每轮乘上 RNDfromCI(0,1) 的区间随机值,等于让步长自身也在衰减或放大,避免后期在最优附近死板跳动。rand1 小于 MoAc 时启用‘除法/乘法’算子:rand2>0.5 用 best/(MoPr+ε) 拉伸,否则用 best*MoPr 压缩,两者都叠了 (rangeMax-rangeMin)*μ 的量纲缩放。 rand1 不小于 MoAc 就切到对称偏移:rand3>0.5 减、否则加 MoPr 倍的区间跨度乘 μ。最后无论哪条路,都用 SeInDiSp 把连续值夹回 [rangeMin,rangeMax] 并按 rangeStep 离散化,保证下轮评估合法。 在 MT5 里把 MoAc 从 0.3 调到 0.7,能明显看到粒子在早期更倾向围绕历史最优做乘除变形而非对称游走;外汇与贵金属参数优化属高风险实验,回测过拟合概率偏高,实盘前务必多周期验证。
class="type">class="kw">double rand3 = u.RNDprobab(); class=class="str">"cmt">// Generate a random value best = cB [c]; class=class="str">"cmt">// Save the current best value μ *= u.RNDfromCI(class="num">0, class="num">1); class=class="str">"cmt">// Change μ if (rand1 < MoAc) class=class="str">"cmt">// If random value is less than MoAc { if (rand2 > class="num">0.5) class=class="str">"cmt">// If random value is greater than class="num">0.5 { a [i].c [c] = best / (MoPr + ϵ) * ((rangeMax [c] - rangeMin [c]) * μ); class=class="str">"cmt">// Update particle position } else { a [i].c [c] = best * (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ); class=class="str">"cmt">// Update particle position } } else class=class="str">"cmt">// If random value is greater than or equal to MoAc { if (rand3 > class="num">0.5) class=class="str">"cmt">// If random value is greater than class="num">0.5 { a [i].c [c] = best - (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ); class=class="str">"cmt">// Update particle position } else { a [i].c [c] = best + (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ); class=class="str">"cmt">// Update particle position } } a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); class=class="str">"cmt">// Convert to discrete values } class=class="str">"cmt">// Research phase class="kw">using Division(D) and Multiplication(M) operators for (class="type">int i = class="num">0; i < popSize; i++) class=class="str">"cmt">// For each particle { for (class="type">int c = class="num">0; c < coords; c++) class=class="str">"cmt">// For each coordinate { class="type">class="kw">double rand1 = u.RNDprobab(); class=class="str">"cmt">// Generate a random value class="type">class="kw">double rand2 = u.RNDprobab(); class=class="str">"cmt">// Generate a random value class="type">class="kw">double rand3 = u.RNDprobab(); class=class="str">"cmt">// Generate a random value best = cB [c]; class=class="str">"cmt">// Save the current best value μ *= u.RNDfromCI(class="num">0, class="num">1); class=class="str">"cmt">// Change μ if (rand1 < MoAc) class=class="str">"cmt">// If random value is less than MoAc { if (rand2 > class="num">0.5) class=class="str">"cmt">// If random value is greater than class="num">0.5 { a [i].c [c] = best / (MoPr + ϵ) * ((rangeMax [c] - rangeMin [c]) * μ); class=class="str">"cmt">// Update particle position } else { a [i].c [c] = best * (MoPr) * ((rangeMax [c] - rangeMin [c]) * μ); class=class="str">"cmt">// Update particle position } } else class=class="str">"cmt">// If random value is greater than or equal to MoAc { if (rand3 > class="num">0.5) class=class="str">"cmt">// If random value is greater than class="num">0.5 {