算术优化算法(AOA):从AOA到SOA(简单优化算法)·综合运用
SOA 粒子位置更新与类初始化
下面这段是简易优化算法(SOA)里粒子位置的概率更新逻辑:以 MoAc 概率直接吸附到当前最优 cB[c],否则以 MoPr 概率在 [rangeMin, rangeMax] 内重掷随机位置;前面的分支则是按 best 加减 μ 倍区间跨度来偏移,最后用 SeInDiSp 把连续值离散化到步长网格。 种群类 C_AO_SOA 默认把 popSize 设为 50、minT=0.1、maxT=0.9、θ=10,并据此把 params 数组扩到 4 格。MT5 里跑这套时,把 popSize 从 50 调到 30 可能加快单代速度但倾向早熟,调到 80 以上则更稳但耗时线性上升。 SetParams 只是把 params[] 的 val 回写进成员变量,所以改参必须走 SetParams 而非直接赋值,否则 Init 阶段读取的还是旧值。外汇与贵金属参数优化属高风险,回测优不代表实盘同效。
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 } } if (u.RNDbool() < MoAc) a [i].c [c] = cB [c]; class=class="str">"cmt">// Set to the best value 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">// Probabilistic update of particle position if (u.RNDbool() < MoAc) a [i].c [c] = cB [c]; class=class="str">"cmt">// Set to the best value else if (u.RNDbool() < MoPr) a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); class=class="str">"cmt">// Generate new random position class="macro">#include "class="macro">#C_AO.mqh" class C_AO_SOA : class="kw">public C_AO { class="kw">public: class=class="str">"cmt">//-------------------------------------------------------------------- ~C_AO_SOA() { } C_AO_SOA() { ao_name = "SOA"; ao_desc = "Simple Optimization Algorithm"; ao_link = "[MQL5官方文档] popSize = class="num">50; class=class="str">"cmt">// Population size minT = class="num">0.1; class=class="str">"cmt">// Minimum T value maxT = class="num">0.9; class=class="str">"cmt">// Maximum T value θ = class="num">10; class=class="str">"cmt">// θ parameter ArrayResize(params, class="num">4); class=class="str">"cmt">// Resize the parameter array class=class="str">"cmt">// Initialize parameters params [class="num">0].name = "popSize"; params [class="num">0].val = popSize; params [class="num">1].name = "minT"; params [class="num">1].val = minT; params [class="num">2].name = "maxT"; params [class="num">2].val = maxT; params [class="num">3].name = "θ"; params [class="num">3].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 θ } 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
「AO_SOA 类的初始化与首轮粒子布点」
下面这段是 AO_SOA 优化器的类声明尾部与 Init / Moving 实现,核心是把连续搜索空间离散化后做首轮随机布点。外汇与贵金属参数寻优里,这种离散网格能压住过拟合倾向,但网格步长设错会直接漏掉最优区,属高风险操作。 Init 方法先调 StandardInit 载入三个数组:rangeMinP 是各维最小边界,rangeMaxP 是最大边界,rangeStepP 是搜索步长;epochsP 默认 0 表示不限制迭代轮数。内部把 epochs 设为传入值,epochNow 归零,ϵ 取 DBL_EPSILON 防除零。 Moving 方法每次调用先 epochNow++。若 revision 标志为假,就对所有 popSize 个粒子、coords 个坐标,用 u.RNDfromCI 在边界内取连续随机数,再经 u.SeInDiSp 吸附到离散步长网格上,最后置 revision=true 并退出。这意味着首轮之后粒子不再随机重生,后续靠 Revision 方法驱动。 开 MT5 把这段代码塞进 EA 的 include 里,先打印 a[0].c[] 看首轮离散值是否踩在你想要的步长上,比直接跑回测更省时间。
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="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="type">class="kw">double ϵ; class=class="str">"cmt">// Parameter to prevent division by zero }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_SOA::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 ϵ 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_SOA::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 }
◍ 探索与修订阶段的粒子更新逻辑
黏菌优化算法在 MT5 里的实现,核心在于用 MoAc 与 MoPr 两个时变概率控制粒子的游走。MoAc 随迭代线性从 minT 爬到 maxT,决定粒子多大概率直接黏附到当前最优坐标;MoPr 按 1-(epochNow/epochs)^(1/θ) 衰减,决定多大概率在参数区间内完全重抽。 下面这段循环就是研究阶段的主体:对每个粒子、每个维度,先掷两次随机布尔。若小于 MoAc 就抄最优解对应维度,若小于 MoPr 就在 [rangeMin,rangeMax] 里重随机,最后用 SeInDiSp 把连续值吸附到离散步长上。外汇或贵金属参数寻优时,离散步长设错会让粒子永远踩不到可行格点,属高风险调试点。 double MoAc = minT + epochNow * ((maxT - minT) / epochs); // 线性递增的黏附概率 double MoPr = 1.0 - pow (epochNow / epochs, (1.0 / θ)); // 随迭代衰减的重抽概率 double best = 0.0; // 暂存最优值 for (int i = 0; i < popSize; i++) // 遍历粒子 { for (int c = 0; c < coords; c++) // 遍历维度 { if (u.RNDbool () < MoAc) a [i].c [c] = cB [c]; // 黏附到最优坐标 else if (u.RNDbool () < MoPr) a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); // 区间内重随机 a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); // 转离散值 } } Revision 函数做收尾修订:扫一遍种群,若某粒子目标值 a[i].f 优于记录值 fB 就更新 fB 并记下标,最后把最优坐标拷回 cB 供下一轮黏附。实盘策略里 fB 初值若未随品种波动尺度归一,EURUSD 与 XAUUSD 混跑时可能让收敛方向偏掉。
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=class="str">"cmt">// Probabilistic update of particle position if (u.RNDbool() < MoAc) a [i].c [c] = cB [c]; class=class="str">"cmt">// Set to the best value else if (u.RNDbool() < MoPr) a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); class=class="str">"cmt">// Generate new 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 } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_SOA::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 }
最后说句实在话
AOA 的核心短板已经很清楚:种群成员之间没有任何信息交换,搜索算子也缺少跨维度的可变性,这让它一旦碰到高维连续问题就容易塌成随机乱搜。SOA 把 MoA 与 MoP 随迭代周期衰减的机制保留下来,改成从最优解复制信息再叠随机扰动,实测里低维离散任务的结果分布明显更窄,但可扩展性依旧偏弱,上到多参数优化就容易掉队。 图例里把颜色等级 ≥0.99 的跑分标白,直方图标尺 0 到 100、100 为理论极值,能直接看出 SOA 在简单函数上聚拢在高位、AOA 则散得很开。外汇与贵金属参数优化属高风险操作,这类算法只管搜索效率,不替你扛过拟合与滑点。 真要上手,把 AO_SOA_SimpleOptimizationAlgorithm.mqh 拖进 MT5 跑 Test_AO_SOA.mq5,先拿两三个参数试,别一上来就丢进全品种遗传式扫参。