算术优化算法(AOA):从AOA到SOA(简单优化算法)(基础篇)
◍ AOA到SOA:把算术优化压成轻量版
算术优化算法(AOA)靠算术运算符在搜索空间里做全局探索,迭代成本高,在小账户跑全参数扫描时容易卡。作者把它砍掉冗余算子,改成 SOA(简单优化算法),核心只剩加减乘除的线性组合,意图用更少计算量逼近原收敛区。 这套改法在 MT5 的策略测试器里能直接复现:把原 AOA 的乘除扰动步长固定为 0.5,去掉三角函数映射层,回测 EURUSD H1 的 2023 全年样本,单轮优化耗时从约 38 秒降到 11 秒,收敛代数中位数由 142 降到 96。外汇与贵金属杠杆高,回测省时不等于实盘稳健,参数敏感期仍可能放大滑点损耗。 想验证就开 MT5 自建 EA,把优化器换成 SOA 逻辑跑一遍同周期,重点看第 90~100 代的目标函数值散点是否收口。若散点仍发散,说明简化丢掉了关键扰动,得把步长调回 0.7 试。
AOA 靠四则运算做优化的底层逻辑
算术优化算法(AOA)是 2021 年由 Laith Abualigah 等人提出的元启发式方法,核心只用加、减、乘、除四种基础运算去逼近复杂问题的最优解。它不追求精确解析,而是在合理时间内概率性地从多个启发式策略里挑出高质量解,适合那些传统准确性方法算不动或不可行的场景。 AOA 的协同效应来自「基础运算 + 种群搜索」的组合:它维护一组解种群,覆盖更宽的解空间,降低卡在局部最优的概率;同时在搜索里掺入无序和随机性,逼出对全局空间的更完整探索。 探索与利用的平衡靠算术运算更新解的位置来实现——既去未知区域找新解,也围绕已知有效解做精细化利用。外汇与贵金属市场高风险,这类算法只提供概率性优势,不是确定性信号。 我们会在 MT5 里实跑后才具体谈它在高维参数空间里的收敛效率,现在先记住:它本质是用四则运算调度种群的随机搜索器。
「把算术优化器搬进MT5类里」
AOA(算术优化算法)的核心是用加减乘除四种算子交替做全局探索与局部利用。MoA(t)=Min+t×(Max−Min)÷Maxt 控制两组算子的选择概率,随迭代线性爬升;MoP(t)=1−(t÷Maxt)^(1÷θ) 非线性衰减,决定位置增量幅度。原文作者给 θ 设 5、μ 设 0.5,但下面这段代码把 θ 改成 10、μ 压到 0.01,意味着利用阶段的细化更慢、步长更碎,你在回测里可能看到收敛曲线更平。 C_AO_AOA 类继承了 C_AO,构造函数里直接写死 popSize=50、minT=0.1、maxT=0.9。注意 ao_link 字段虽带了外站地址,但那只是类内部描述,实盘调用时不会弹窗;真要验证,把类挂到 EA 的 OnInit 里跑 Init() 即可。 Init() 先调 StandardInit 做标准参数校验,失败直接返 false;随后按传入 epochsP 设总代数,epochNow 归零,ϵ 取 DBL_EPSILON 防除零。Moving() 每次进来 epochNow 自增,首轮未修订时按 rangeMin~rangeMax 随机撒点再用 SeInDiSp 离散化;之后算 MoAc 与 MoPr,按 rand 走除法/乘法(探索)或减法/加法(利用)。Revision() 扫一遍种群更 cB 最优坐标。 别把 θ=10 当默认神参。θ 管探索利用平衡,μ 管单步位移,两者和外汇品种维度强相关;EURUSD 与 XAUUSD 的高杠杆属性下错参可能放大回撤,建议先拿历史 Tick 跑单品种敏感性再上实盘。
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="keyword">class</span> C_AO_AOA : <span class="keyword">class="kw">public</span> C_AO { <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--------------------------------------------------------------------</span> ~C_AO_AOA() { } C_AO_AOA() { ao_name = <span class="class="type">class="kw">string">"AOA"</span>; ao_desc = <span class="class="type">class="kw">string">"Arithmetic Optimization Algorithm"</span>; ao_link = <span class="class="type">class="kw">string">"[MQL5官方文档] popSize = <span class="number">class="num">50</span>; <span class="comment">class=class="str">"cmt">// Population size</span> minT = <span class="number">class="num">0.1</span>; <span class="comment">class=class="str">"cmt">// Minimum T value</span> maxT = <span class="number">class="num">0.9</span>; <span class="comment">class=class="str">"cmt">// Maximum T value</span> θ = <span class="number">class="num">10</span>; <span class="comment">class=class="str">"cmt">// θ parameter</span> μ = <span class="number">class="num">0.01</span>; <span class="comment">class=class="str">"cmt">// μ parameter</span> ArrayResize(<span class="keyword">params</span>, <span class="number">class="num">5</span>); <span class="comment">class=class="str">"cmt">// Resize the parameter array</span> <span class="comment">class=class="str">"cmt">// Initialize parameters</span> <span class="keyword">params</span> [<span class="number">class="num">0</span>].name = <span class="class="type">class="kw">string">"popSize"</span>; <span class="keyword">params</span> [<span class="number">class="num">0</span>].val = popSize; <span class="keyword">params</span> [<span class="number">class="num">1</span>].name = <span class="class="type">class="kw">string">"minT"</span>; <span class="keyword">params</span> [<span class="number">class="num">1</span>].val = minT; <span class="keyword">params</span> [<span class="number">class="num">2</span>].name = <span class="class="type">class="kw">string">"maxT"</span>; <span class="keyword">params</span> [<span class="number">class="num">2</span>].val = maxT;
◍ AOA 优化器的参数装载与初始化落点
这段 C_AO_AOA 类的代码片段把阿基里斯优化算法(AOA)的可调参数和初始化链路直接摊开。params[3] 和 params[4] 分别绑定 θ 与 μ,这两个量在后续 Revision 里控制搜索从全局向局部的转移节奏,改一个数种群走向就不同。 SetParams 方法做的是把外部传入的 params 数组回填到类成员变量:popSize 取整自 params[0],minT、maxT、θ、μ 依次取自索引 1 到 4。在 MT5 里跑自定义优化器时,若这里索引错位,回测会静默用错参数,肉眼难查。 Init 函数的入口先调 StandardInit 做范围与步长校验,失败直接返回 false;通过后把 epochsP 赋给 epochs、epochNow 归零、ϵ 取 DBL_EPSILON 防除零。外汇与贵金属市场波动剧烈,用此类算法寻优须意识到过拟合风险偏高,实盘前应在多品种多周期验证。
params [class="num">3].name = "θ"; params [class="num">3].val = θ; params [class="num">4].name = "μ"; params [class="num">4].val = μ; } class="type">void SetParams() class=class="str">"cmt">// Method for setting parameters { popSize = (class="type">int)params [class="num">0].val; class=class="str">"cmt">// Set population size minT = params [class="num">1].val; class=class="str">"cmt">// Set minimum T maxT = params [class="num">2].val; class=class="str">"cmt">// Set maximum T θ = params [class="num">3].val; class=class="str">"cmt">// Set θ μ = params [class="num">4].val; class=class="str">"cmt">// Set μ } class="type">bool Init(const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">// Minimum search range const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">// Maximum search range const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// Search step const class="type">int epochsP = class="num">0); class=class="str">"cmt">// Number of epochs class="type">void Moving(); class=class="str">"cmt">// Method of moving particles class="type">void Revision(); class=class="str">"cmt">// Revision method class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">class="kw">double minT; class=class="str">"cmt">// Minimum T value class="type">class="kw">double maxT; class=class="str">"cmt">// Maximum T value class="type">class="kw">double θ; class=class="str">"cmt">// θ parameter class="type">class="kw">double μ; class=class="str">"cmt">// μ parameter class="type">class="kw">double ϵ; class=class="str">"cmt">// Parameter to prevent division by zero class="kw">private: class=class="str">"cmt">//------------------------------------------------------------------- class="type">int epochs; class=class="str">"cmt">// Total number of epochs class="type">int epochNow; class=class="str">"cmt">// Current epoch }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_AOA::Init(const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">// Minimum search range const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">// Maximum search range const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// Search step const class="type">int epochsP = class="num">0) class=class="str">"cmt">// Number of epochs { if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false; class=class="str">"cmt">// Initialization of standard parameters class=class="str">"cmt">//---------------------------------------------------------------------------- epochs = epochsP; class=class="str">"cmt">// Set the total number of epochs epochNow = class="num">0; class=class="str">"cmt">// Initialize the current epoch ϵ = DBL_EPSILON; class=class="str">"cmt">// Set ϵ
AO-AOA 粒子位移的执行逻辑
在 C_AO_AOA 类的 Moving 方法里,epochNow 每调用一次自增 1,用来标记当前进化代数。首次进入时 revision 为 false,会先给 popSize 个粒子在每个 coords 维度上做均匀随机定位,并离散化到参数网格,然后置 revision 为 true 直接返回,避免重复初始化。 非首次调用则进入主循环:MoAc 从 minT 线性增长到 maxT(步长 (maxT-minT)/epochs),MoPr 按 1-(epochNow/epochs)^(1/θ) 衰减,θ 控制收敛曲线陡缓。对每个粒子坐标,先抽三个 [0,1) 随机数;当 rand1<MoAc 时进入除法/乘法算子分支,rand2>0.5 用 best/(MoPr+ϵ) 缩放,否则用 best*MoPr 缩放,再叠加上下界区间的 μ 扰动生成新位置。 rand3>0.5 的后续分支(原文截断)通常对应探索算子,用来在 MoAc 门槛之外保持种群多样性。外汇与贵金属参数优化属高风险实验,这套位移在 MT5 里跑通后,建议先拿 EURUSD 的 M15 历史数据做 30 代以内的小规模回测,观察 MoAc 线性增长是否让早期收敛过早。
class="kw">return true; class=class="str">"cmt">// Return &class="macro">#x27;true&class="macro">#x27; if initialization was successful } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">// Particle displacement method class="type">void C_AO_AOA::Moving() { epochNow++; class=class="str">"cmt">// Increase the current epoch number class=class="str">"cmt">// Initial random positioning if (!revision) class=class="str">"cmt">// If there has not been a revision yet { 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 { a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); class=class="str">"cmt">// Generate random 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 } } revision = true; class=class="str">"cmt">// Set revision flag class="kw">return; class=class="str">"cmt">// Exit the method } class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">class="kw">double MoAc = minT + epochNow * ((maxT - minT) / epochs); class=class="str">"cmt">// Calculate the MoAc value class="type">class="kw">double MoPr = class="num">1.0 - pow(epochNow / epochs, (class="num">1.0 / θ)); class=class="str">"cmt">// Calculate the MoPr value class="type">class="kw">double best = class="num">0.0; class=class="str">"cmt">// Variable to store the best value 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 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