种群优化算法:类电磁算法(EM - ElectroMagnetism)·进阶篇
电荷粒子群的初始化与受力计算
这段逻辑实现了一个类电荷系统的粒子群初始化与两两受力推导,常用于多参数寻优场景,比如外汇 EA 的参数空间搜索。先在不重置 revision 时给每个粒子逐维撒点:用 RNDfromCI 在 [rangeMin, rangeMax] 内取随机值,再用 SeInDiSp 按 rangeStep 对齐到离散网格,同时把个体最优适应度 f 压到 -DBL_MAX(约 -1.797e308),保证首轮任何评估都能刷新。 全局最优 fB 同样初始化为 -DBL_MAX,vect 数组记录各维区间长度(rangeMax - rangeMin),revision 置 true 避免重复撒点。随后进入电荷计算:先累加 sumDiff = Σ(fB - p[obj].f),若 sumDiff 为 0 则所有粒子电荷 C 直接归零,否则按 exp(-粒子数 × (fB - 个体f)/sumDiff) 赋电荷,适应度越接近全局最优的粒子电荷越大。 力场部分用双重循环算欧氏距离:p[obj].R[obj2] = sqrt(Σ(c维坐标差的平方)),对称存储到 p[obj2].R[obj]。库仑力大小 Fp = C_obj × C_obj2 / (4π × envConstant × R^exponent),再按适应度高低决定推力方向——优粒子把劣粒子推离、自身被反向拉,劣粒子反之。 开 MT5 把下面代码贴进自定义函数的初始化段,改 particlesNumber=30、coordinatesNumber=5 跑一遍,能在观察窗口看到 R 矩阵对称且 F 向量非零,说明力场已激活。外汇与贵金属杠杆高,这类寻优仅作策略研究,实盘前须充分回测。
ArrayResize(p[i].R, particlesNumber); ArrayResize(p[i].F, coordinatesNumber); p[i].f = -DBL_MAX; } ArrayResize(cB, coordinatesNumber); } if(!revision) { fB = -DBL_MAX; for(class="type">int obj=class="num">0; obj<particlesNumber; obj++) { for(class="type">int c=class="num">0; c<coordinatesNumber; c++) { p[obj].c[c] = RNDfromCI(rangeMin[c], rangeMax[c]); p[obj].c[c] = SeInDiSp(p[obj].c[c], rangeMin[c], rangeMax[c], rangeStep[c]); p[obj].C = class="num">0.0; p[obj].f = -DBL_MAX; } } for(class="type">int c=class="num">0; c<coordinatesNumber; c++) { vect[c] = rangeMax[c] - rangeMin[c]; } revision = true; } for(class="type">int obj=class="num">0; obj<particlesNumber; obj++) { sumDiff += fB - p[obj].f; } p[obj].C = exp(-particlesNumber * ((fB - p[obj].f) / sumDiff)); for(class="type">int obj=class="num">0; obj<particlesNumber; obj++) { if(sumDiff == class="num">0.0) { p[obj].C = class="num">0.0; } else { p[obj].C = exp(-particlesNumber * ((fB - p[obj].f) / sumDiff)); } } for(class="type">int obj=class="num">0; obj<particlesNumber; obj++) { ArrayInitialize(p[obj].R, class="num">0.0); ArrayInitialize(p[obj].F, class="num">0.0); } for(class="type">int obj=class="num">0; obj<particlesNumber; obj++) { for(class="type">int obj2=class="num">0; obj2<particlesNumber; obj2++) { if(obj != obj2) { if(p[obj].R[obj2] == class="num">0.0) { for(class="type">int c=class="num">0; c<coordinatesNumber; c++) { diffDist = p[obj].c[c] - p[obj2].c[c]; p[obj].R[obj2] += diffDist * diffDist; } p[obj].R[obj2] = sqrt(p[obj].R[obj2]); p[obj2].R[obj] = p[obj].R[obj2]; Fp = p[obj].C * p[obj2].C / (class="num">4.0 * M_PI * envConstant * pow(p[obj].R[obj2], exponent)); for(class="type">int c=class="num">0; c<coordinatesNumber; c++) { if(p[obj].f > p[obj2].f) { p[obj].F[c] += (p[obj2].c[c] - p[obj].c[c]) * Fp; p[obj2].F[c] -= (p[obj2].c[c] - p[obj].c[c]) * Fp; } else { p[obj].F[c] -= (p[obj2].c[c] - p[obj].c[c]) * Fp; p[obj2].F[c] += (p[obj2].c[c] - p[obj].c[c]) * Fp; } } } } } }
「粒子位移与离散空间约束的实现细节」
这段逻辑处在进化算法每代迭代的核心:先按随机扰动更新每个粒子的坐标,再做边界收敛与最优解记录。 外层双循环遍历 particlesNumber 个粒子、coordinatesNumber 维坐标。r 由 RNDfromCI(0.0,1.0) 取均匀随机数,新坐标 = 原坐标 + r × 受力 F × 方向向量 vect × 移动常数 movConstant,相当于给每一步位移加了随机缩放,避免陷入过早收敛。 被注释的两行是环形越界处理,实际没启用;取而代之的是 SeInDiSp() 把连续坐标吸附到 [rangeMin, rangeMax] 内、按 rangeStep 离散化的合法格点。若参数 Step 设为 0.0 则不做离散,直接返回原值。 Revision() 在每代末尾跑一遍:只要某粒子适应度 p[s].f 超过全局最优 fB,就更新 fB 并用 ArrayCopy 把该粒子坐标存进 cB。这是精英保留,保证历史最优不会被随机游走冲掉。 SeInDiSp 的四舍五入用 MathRound((In-InMin)/Step),意味着你的 rangeStep 若设 0.01,坐标就会落在 0.01 的整数倍上——调参时步长直接决定搜索粒度,外汇与贵金属参数优化属高风险,过细步长可能过拟合历史行情。
for (class="type">int obj = class="num">0; obj < particlesNumber; obj++) { for (class="type">int c = class="num">0; c < coordinatesNumber; c++) { r = RNDfromCI(class="num">0.0, class="num">1.0); p [obj].c [c] = p [obj].c [c] + r * p [obj].F [c] * vect [c] * movConstant; class=class="str">"cmt">//if (p [obj].c [c] > rangeMax [c]) p [obj].c [c] = rangeMin [c] + (p [obj].c [c] - rangeMax [c]); class=class="str">"cmt">//if (p [obj].c [c] < rangeMin [c]) p [obj].c [c] = rangeMax [c] - (rangeMin [c] - p [obj].c [c]); p [obj].c [c] = SeInDiSp(p [obj].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } class="type">void C_AO_EM::Revision() { for (class="type">int s = class="num">0; s < particlesNumber; s++) { if (p [s].f > fB) { fB = p [s].f; ArrayCopy(cB, p [s].c, class="num">0, class="num">0, WHOLE_ARRAY); } } } class="type">class="kw">double C_AO_EM::SeInDiSp(class="type">class="kw">double In, class="type">class="kw">double InMin, class="type">class="kw">double InMax, class="type">class="kw">double Step) { if (In <= InMin) class="kw">return (InMin); if (In >= InMax) class="kw">return (InMax); if (Step == class="num">0.0) class="kw">return (In); else class="kw">return (InMin + Step * (class="type">class="kw">double)MathRound((In - InMin) / Step)); }
◍ EM 算法的实测得分与反直觉现象
把类电磁算法(EM)接进 MT5 的试验台跑了一遍,三组基准函数各做 5 / 25 / 500 次重复、每次函数调用 10000 回。Rastrigin 在 500 维下 result 为 63.9495、Score 0.79237,Forest 与 Megacity 同维度分别只有 0.06638 和 0.02860,全函数合计 Score 2.79996。 横向拉表看,EM 总评 22.091 分,在 14 个对比算法里排倒数第四,仅好于猴子、鱼群、PSO、随机和灰狼。10 维与 50 维的 Rastrigin、Forest、Megacity 上它几乎垫底,Forest 10 维 Score 直接是 0.00000。 反直觉的是 1000 参数(500 F)那栏:Rastrigin 上 EM 拿了 1.00000 满分,反而压过 SSG 的 0.55665 和 BA 的 0.84451。这是整张表里第一次出现搜索质量随参数规模上升而变好的情况,大概率和它电荷分组式的搜索策略、以及对全域可微性的依赖有关。 外汇与贵金属行情里套这类高维优化要当心,EM 对梯度敏感、低维容易躺平,实盘参数空间若不可微,收敛质量可能明显低于回测表现。
选型前先看清代价
电磁算法(EM)在平滑、高维函数上确实能拿到高精度,外部参数少、不依赖梯度,这是它适合拿来跑 EA 参数寻优的底子。但实测里它对离散函数和带平坦“平台”的目标函数会卡死,计算复杂度也随维度陡增,MT5 优化器里直接套未必比内置遗传快。 作者公布的对比直方图显示,在连续平滑测试函数上 EM 收敛误差普遍低于 1e-3,而在阶梯状函数上失败率超过 40%。这种反差意味着:拿它扫外汇品种的多周期参数,先确认目标面是否够光滑。 每篇附的 ZIP(如 16_The_world_of_AO_EM.zip,128.45 KB)含可编译 MQL5 源码,想验证直接下回来跑。算法没有万能,选 EM 还是海豚、骆驼,得看你的变量数和函数形态,别凭名字新鲜就换。