人工电场算法(AEFA)·进阶篇
人工电场算法里的参数容器与初始化骨架
这段代码片段展示了一个基于人工电场优化(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。外汇与贵金属市场波动剧烈、杠杆风险高,这类群体智能优化仅用于参数搜索,实盘表现仍可能大幅偏离回测。
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)可先看收敛速度,再决定是否加大种群。
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。
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 里的源码挂上测。