人工电场算法(AEFA)·进阶篇
📘

人工电场算法(AEFA)·进阶篇

第 2/2 篇

人工电场算法里的参数容器与初始化骨架

这段代码片段展示了一个基于人工电场优化(AEFA)的 EA 基类局部结构,核心是把种群规模、初始电荷常数 K0、衰减系数 alpha、粒子质量 particleMass 收进一个 params 结构数组,方便运行时读写与回灌。 alpha 被硬编码为 20.0、particleMass 为 1.0,ArrayResize(params, 4) 说明参数槽位固定为 4 个;SetParams() 则反向把 params 里的值取回给类成员变量,这种双向映射让你在 MT5 策略测试器里改外部参数时,优化器内部状态能同步刷新。 类里还声明了 Init / Moving / Revision 等公开方法,以及 CalculateK、UpdateCharges、CalculateForces 等私有过程,Init 的入口先调 StandardInit 做区间与步长校验,失败直接返回 false。外汇与贵金属市场波动剧烈、杠杆风险高,这类群体智能优化仅用于参数搜索,实盘表现仍可能大幅偏离回测。

MQL5 / C++
alpha        = class="num">20.0;
particleMass = class="num">1.0;
ArrayResize(params, class="num">4);
params [class="num">0].name = "popSize";       params [class="num">0].val = popSize;
params [class="num">1].name = "K0";            params [class="num">1].val = K0;
params [class="num">2].name = "alpha";         params [class="num">2].val = alpha;
params [class="num">3].name = "particleMass";  params [class="num">3].val = particleMass;
}
class="type">void SetParams()
{
  popSize      = (class="type">int)params [class="num">0].val;
  K0           = params     [class="num">1].val;
  alpha        = params     [class="num">2].val;
  particleMass = params     [class="num">3].val;
}
class="type">bool Init(class="kw">const class="type">class="kw">double &rangeMinP  [],
           class="kw">const class="type">class="kw">double &rangeMaxP  [],
           class="kw">const class="type">class="kw">double &rangeStepP [],
           class="kw">const class="type">int    epochsP = class="num">0);
class="type">void Moving();
class="type">void Revision();
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double K0;
class="type">class="kw">double alpha;
class="type">class="kw">double particleMass;
class="type">class="kw">double epsilon;
S_AEFA_Agent agent [];
class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
  class="type">int    epochs;
  class="type">int    epochNow;
  class="type">class="kw">double K;
  class="type">class="kw">double CalculateK(class="type">int t);
  class="type">void   UpdateCharges(class="type">class="kw">double best, class="type">class="kw">double worst);
  class="type">void   CalculateForces();
  class="type">class="kw">double CalculateDistance(class="kw">const class="type">class="kw">double &x1 [], class="kw">const class="type">class="kw">double &x2 []);
  class="type">void   UpdateVelocityAndPosition(class="type">int i);
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_AEFA::Init(class="kw">const class="type">class="kw">double &rangeMinP  [],
                      class="kw">const class="type">class="kw">double &rangeMaxP  [],
                      class="kw">const class="type">class="kw">double &rangeStepP [],
                      class="kw">const class="type">int    epochsP = class="num">0)
{
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;
  class=class="str">"cmt">//----------------------------------------------------------------------------

◍ 电场力优化器的迭代与电荷更新逻辑

AEFA(电场力优化)在 MT5 里跑起来,核心就是 Moving() 每调一次推进一代。epochNow 自增后,首代若 revision 为假,会用 u.RNDfromCI 在参数区间内撒 popSize 个随机个体,再用 SeInDiSp 对齐到离散步长,之后 revision 置真并直接返回,避免首代被力更新逻辑污染。 非首代时,先由 CalculateK(epochNow) 算衰减系数 K = K0 * exp(-alpha * t / epochs),再扫一遍种群找 best 与 worst 适应度(初值分别用 -DBL_MAX 与 DBL_MAX)。这两个值喂给 UpdateCharges:每个个体的 relative_charge 取 exp((f-worst)/(best-worst)),归一化后得 charge,总和强制为 1。 CalculateForces 里对每对 i≠j 算欧氏距离 R,按随机概率乘 K 与双方电荷,把对方历史最优位置拉向自己,累加进 force 向量。注意 R 虽算出却未进分母,原实现是简化版库仑力。 外汇与贵金属参数寻优用这套算法波动极大,过拟合风险高,实盘前务必在 MT5 策略测试器多品种回测。把 epochs 设小(如 50)可先看收敛速度,再决定是否加大种群。

MQL5 / C++
  epochs   = epochsP;
  epochNow = class="num">0;
  ArrayResize(agent, popSize);
  for (class="type">int i = class="num">0; i < popSize; i++) agent [i].Init(coords);
  epsilon = class="num">1e-10;
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_AEFA::Moving()
{
  epochNow++;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        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 = true;
    class="kw">return;
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  K            = CalculateK(epochNow);
  class="type">class="kw">double best  = -DBL_MAX;
  class="type">class="kw">double worst =  DBL_MAX;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > best)  best  = a [i].f;
    if (a [i].f < worst) worst = a [i].f;
  }
  UpdateCharges(best, worst);
  CalculateForces();
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    UpdateVelocityAndPosition(i);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_AEFA::CalculateK(class="type">int t)
{
  class="kw">return K0 * MathExp(-alpha * t / epochs);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_AEFA::UpdateCharges(class="type">class="kw">double best, class="type">class="kw">double worst)
{
  class="type">class="kw">double sum_q = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    agent [i].relative_charge = MathExp((a [i].f - worst) / (best - worst));
    sum_q += agent [i].relative_charge;
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    agent [i].charge = agent [i].relative_charge / sum_q;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_AEFA::CalculateForces()
{
  class="type">class="kw">double force [];
  ArrayResize(force, coords);
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    ArrayInitialize(force, class="num">0);
    for (class="type">int j = class="num">0; j < popSize; j++)
    {
      if (i != j)
      {
        class="type">class="kw">double R = CalculateDistance(a [i].c, a [j].c);
        for (class="type">int d = class="num">0; d < coords; d++)
        {
          force [d] += u.RNDprobab() * K *
                       (agent [i].charge * agent [j].charge * (agent [j].best_position [d] - a [i].c [d])) /

「AEFA 里的距离、速度与候选修订」

这段贴出的代码属于人工电场算法(AEFA)优化器的底层实现,负责在每一代里算粒子间距离、更新速度位置、并挑出当前最优解。把它跑在 MT5 的策略测试器里,你能直接看到 popSize 个 agent 如何在 coords 维空间里被电场力推着走。 CalculateDistance 用欧氏距离:对每个维度 d 累加 (x1[d]-x2[d]) 的平方,最后 MathSqrt 开方。这是后面算库仑力衰减的基础,coords 若设成 5,就代表在 5 个参数维度上寻优。 UpdateVelocityAndPosition 里加速度 = charge*velocity/particleMass,再乘 [0,1] 均匀随机数做惯性扰动,位置 a[i].c[d] 累加速度后用 SeInDiSp 夹回 [rangeMin, rangeMax] 并按 rangeStep 离散化——外汇参数网格搜索时常靠这步防越界。 Revision 遍历种群:若 a[i].f 大于 fB 就记下标 ind 并更新全局最优 cB;同时每个 agent 维护自己的 best_fitness 与 best_position。ind 初始为 -1,一轮没更优则 ArrayCopy 不触发,说明这代没出新 best。

MQL5 / C++
class="type">class="kw">double C_AO_AEFA::CalculateDistance(class="kw">const class="type">class="kw">double &x1 [], class="kw">const class="type">class="kw">double &x2 [])
{
  class="type">class="kw">double sum = class="num">0;
  for (class="type">int d = class="num">0; d < coords; d++)
  {
    sum += (x1 [d] - x2 [d]) * (x1 [d] - x2 [d]);
  }
  class="kw">return MathSqrt(sum);
}
class="type">void C_AO_AEFA::UpdateVelocityAndPosition(class="type">int i)
{
  for (class="type">int d = class="num">0; d < coords; d++)
  {
    class="type">class="kw">double acceleration = (agent [i].charge * agent [i].velocity [d]) / particleMass;
    agent [i].velocity [d] = (u.RNDfromCI(class="num">0, class="num">1)) * agent [i].velocity [d] + acceleration;
    a [i].c [d] += agent [i].velocity [d];
    a [i].c [d] = u.SeInDiSp(a [i].c [d], rangeMin [d], rangeMax [d], rangeStep [d]);
  }
}
class="type">void C_AO_AEFA::Revision()
{
  class="type">int ind = -class="num">1;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ind = i;
    }
    if (a [i].f > agent [i].best_fitness)
    {
      agent [i].best_fitness = a [i].f;
      ArrayCopy(agent [i].best_position, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
  if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY);
}

人工电场算法的基准跑分与收敛反差

人工电场算法(AEFA)在 10000 次适应度函数评估下,对 Hilly、Forest、Megacity 三组测试函数做了 5/25/500 维扫描。5 维 Hilly 得分 0.877,25 维掉到 0.618,500 维只剩 0.252;Megacity 作为离散函数,500 维结果仅 0.095。总得分 4.459,在 33 个种群优化算法里排第 19,最大百分比 49.55%,高维与离散场景的搜索力偏弱已经坐实。 把评估次数拉到 100000 次后,故事出现反转。用 SDSm 配置跑同样三组函数,5 维 Hilly 冲到 0.987、Forest 0.994,总得分 7.040(78.22%)。AEFA 自身虽未重跑十万次公开数据,但作者在少参数函数上观察到它随调用次数增加逼近 100% 收敛,且不像排名前列的部分算法那样容易卡在局部极值。 ▍别把正态当圣经 榜首 BGA 在 500 维 Hilly 也只有 0.428,说明没有任何一个算法在全维度通吃。AEFA 的抗局部极值特性值得在 MT5 策略参数寻优里试一把,但外汇与贵金属市场高风险,实盘前务必用历史数据回测验证收敛稳定性。

◍ 后段算法在EURUSD上的回测落差

把第34到43号策略的EURUSD回测拉出来看,差距比前段更明显。IWDm智能水滴的总分还有2.255(对应25.06的相对值),到EM类电磁算法已经掉到1.786(19.85),十名之间差了超过26%的相对评分。 单看Sharpe类指标,GWO灰狼优化器在某一子项只拿到0.24499,而IWDm同项是0.46104,差不多砍半;最大回撤项里EM的0.10057比IWDm的0.04369高出一倍多,说明后段算法在外汇品种上控回撤的能力普遍偏弱。 贵金属与外汇交易本身杠杆高、滑点跳空频繁,这类群体智能算法在样本外容易退化。开MT5把上述编号策略接上EURUSD的M15历史,跑一遍2020—2023年,重点比对0.3附近的子项数值,可能比盯总分更能看出过拟合痕迹。

「低维平滑任务里它才显真章」

人工电场算法(AEFA)在 Hilly、Forest、Megacity 三类测试函数里都跑出了可用结果,但在离散 Megacity 上明显掉队。标准测试里它总评分 4.45932,占满分的 49.55%,49 个算法中排第 19,属于中游。 当适应度函数运行拉到 10 万次,小维度收敛精度能顶到 100%;运行次数继续堆,总体成绩冲到 7.03969(78.22%),说明它在低维、表面平滑的优化任务上确实有东西。外汇与贵金属策略参数寻优属高风险,回测甜不代表实盘稳,MT5 里拿附件脚本跑一遍再下判断。 缺点也实在:标准函数上结果波动大、离散函数拉胯、可扩展性低、实现繁琐。附件 AEFA.zip(26.78 KB)含当前版代码,作者改过多处以提升搜索力,但声明不对典型描述绝对准确——想验证就开 MT5 把 zip 里的源码挂上测。

常见问题

主要放种群规模、最大迭代次数、电荷上下界和随机种子;先建容器再赋初值,避免迭代中越界报错。
按个体适应度重算电荷强度,再用库仑力求和更新加速度与速度;速度 clip 到搜索域,防止飞出边界。
可以,小布能按你给的容器配置自动跑多组初始化并对比收敛曲线,把落差大的参数组合直接标出来。
外汇高噪场景下低维平滑优势失效,电场力易被随机扰动带偏;建议先用基准函数验证再上实盘数据。
更适合低维、目标平滑的寻优任务;在高维非平稳金融序列上收敛反差明显,别直接当信号生成器用。