算术优化算法(AOA):从AOA到SOA(简单优化算法)·综合运用
📘

算术优化算法(AOA):从AOA到SOA(简单优化算法)·综合运用

第 3/3 篇

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 阶段读取的还是旧值。外汇与贵金属参数优化属高风险,回测优不代表实盘同效。

MQL5 / C++
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[] 看首轮离散值是否踩在你想要的步长上,比直接跑回测更省时间。

MQL5 / 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 混跑时可能让收敛方向偏掉。

MQL5 / C++
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,先拿两三个参数试,别一上来就丢进全品种遗传式扫参。

常见问题

先完成 SOA 类构造并做首轮粒子随机布点,再进入位置更新;顺序反了会读到空种群导致报错。
首轮应在搜索空间边界内均匀撒点,避免聚堆;可用随机种子固定以便复现实验。
可以,小布能按你给的边界和迭代次数批量跑粒子布点与更新逻辑,直接回传收敛曲线和较优参数。
探索阶段靠大步长随机扰动找范围,修订阶段转局部精细修正;切换由当前迭代占比控制。
仅作概率倾向参考,外汇贵金属高风险,需样本外验证并严控仓位,别当确定性结论用。