种群优化算法:类电磁算法(EM - ElectroMagnetism)(基础篇)
📘

种群优化算法:类电磁算法(EM - ElectroMagnetism)(基础篇)

第 1/2 篇

「用类电磁机制给参数找优解」

类电磁算法(EM)把每个候选参数集当成带电粒子,用库仑式的引斥力在解空间里扰动搜索,属于种群优化的一类。它不靠梯度,适合 MT5 里那种目标函数坑洼、不可导的EA参数寻优。 在 2023-08-29 发布的示例里,作者用该算法跑过一轮测试,样本计数显示 1 331 次观测,说明解空间被扫得不算浅。外汇与贵金属杠杆高,回测漂亮不等于实盘能复现,任何优化结果都只是提高概率。 真要验证,开 MT5 把下面这段 EM 的电荷力计算抄进脚本,改两个参数就能看粒子怎么动。

◍ 类电磁算法从物理机制到优化工具

类电磁(EM)算法是 2003 年由 I. Birbil 与 S.C. Fang 提出的元启发式搜索方法,模拟物理空间中带电粒子的吸引与排斥行为,属于带随机噪声的群体进化算法。它面向连续域无约束非线性优化,靠电荷间相互作用在解空间里探索,已被用于不少复杂全局优化场景。 其物理底层只有正、负两类电荷,电荷量可正可负;电磁场能以无线电波传信息,地球磁场则帮我们挡掉太阳风与宇宙射线。自然界四种基本力里,电磁力与引力、弱力、强力并列,这也是该算法命名与机制来源的硬背景。 对交易者而言,EM 的价值不在物理现象本身,而在它提供了一种不依赖梯度、靠种群随机扰动找最优参数的思路。MT5 里若要做参数寻优,可借这类机制替代穷举,但外汇/贵金属市场高风险,任何优化结果都只是概率倾向,需实盘前用历史数据严格验证。

把解当电荷来搜:EM 优化器的粒子与力学

电磁优化(EM)把每个候选解看成搜索空间里的带电粒子,坐标向量决定位置,目标函数输出映射成电荷。目标值越好的粒子电荷越强,对邻域施加吸引力;目标值差的粒子产生排斥力,把弱解推离劣质区。算法从一组随机电荷起步,每次迭代按库仑式相互作用更新坐标,若干轮后解群会向全局较优区收敛。 逻辑单元是 S_Particle 结构:c[] 存坐标,C 是电荷,f 是适应度,R[] 是到其它粒子的距离,F[] 是合力矢量。C_AO_EM 类持有粒子数组 p[]、各维 rangeMax/rangeMin/rangeStep、最佳坐标 cB 与最佳值 fB;Init() 负责按坐标数、粒子数、环境常数和步长开数组并置初值,Moving(int iter) 管每轮位移,Revision() 管越界修订与最优跟踪。 Moving() 先判初始化:未初始化就给每个粒子随机坐标、清零电荷,并算各维极差向量 vect[]。已初始化则按「该粒子距全局最大偏差 / 所有粒子距全局最大偏差之和」归一化出电荷(偏差和为零则电荷为零)。接着清空距离与力数组,两两算欧氏距离,用库仑定律叠加受力——力与电荷乘积成正比、与距离成反比。作者原建议对合力归一化,但我实测不归一化结果更好,下面代码即未归一化版本。 粒子无质量,所以没有加速度阶段,直接按力矢量平移到新坐标,位移受 range 与 step 限制。Revision() 在每轮末尾扫一遍粒子,若某粒子 f 大于当前 fB 就刷新 fB 并把坐标拷进 cB,相当于在线记账最优解。SeInDiSp() 则是把越界变量夹回 [InMin,InMax] 并按步长对齐网格,避免无效探索。 外汇与贵金属参数寻优用这套有高波动风险,回测过拟合概率不低,建议拿 MT5 策略测试器先跑 3~5 个品种验证。

MQL5 / C++
class="kw">struct S_Particle
{
  class="type">class="kw">double c  [];   class=class="str">"cmt">//coordinates
  class="type">class="kw">double C;       class=class="str">"cmt">//charge
  class="type">class="kw">double f;       class=class="str">"cmt">//fitness
  class="type">class="kw">double R  [];   class=class="str">"cmt">//euclidean distance to other particles
  class="type">class="kw">double F  [];   class=class="str">"cmt">//force vector
};

class C_AO_EM
{
  class="kw">public: S_Particle p      []; class=class="str">"cmt">//particles
  class="kw">public: class="type">class="kw">double rangeMax   []; class=class="str">"cmt">//maximum search range
  class="kw">public: class="type">class="kw">double rangeMin   []; class=class="str">"cmt">//minimum search range
  class="kw">public: class="type">class="kw">double rangeStep []; class=class="str">"cmt">//step search
  class="kw">public: class="type">class="kw">double cB         []; class=class="str">"cmt">//best coordinates
  class="kw">public: class="type">class="kw">double fB;            class=class="str">"cmt">//FF of the best coordinates
  class="kw">public: class="type">void Init(class="kw">const class="type">int      coordinatesNumberP, class=class="str">"cmt">//coordinates number
                     class="kw">const class="type">int      particlesNumberP,   class=class="str">"cmt">//particles number

「极值优化器的初始化落地」

上面这段是 C_AO_EM 类的构造尾部与 Init 方法,把蚁群式极值搜索器的参数和内存一次性钉死。coordinatesNumber 决定搜索空间维度,particlesNumber 是粒子数,envConstant、movConstant、exponent 三者共同控制游走步长与环境牵引强度,外汇与贵金属行情里这类参数对噪声极敏感,调错容易过拟合,属高风险操作。 Init 里第一行 MathSrand((int)GetMicrosecondCount()) 用微秒级种子重置随机发生器,避免每次回测得到同一串伪随机轨迹。随后 fB 置为 -DBL_MAX 作为历史最优值的初始负无穷,revision 置 false 表示尚未进入修正态。 五个 ArrayResize 分别给 rangeMax、rangeMin、rangeStep、vect 按坐标数开内存,给 p 按粒子数开内存;紧接着的 for 循环再给每个粒子的 c 数组按坐标数扩容。你在 MT5 里新建 EA 时,若粒子数设 50、坐标数设 4,这里就会实打实分配 50×4 的双精度槽位,开终端调试能直接看到数组大小变化。

MQL5 / C++
class="kw">const class="type">class="kw">double envConstantP,      class=class="str">"cmt">//environmental constant
class="kw">const class="type">class="kw">double movConstantP,      class=class="str">"cmt">//movement step
class="kw">const class="type">class="kw">double exponentP);        class=class="str">"cmt">//exponent
class="kw">public: class="type">void Moving();
class="kw">public: class="type">void Revision();
class=class="str">"cmt">//----------------------------------------------------------------------------
class="kw">private: class="type">int    coordinatesNumber; class=class="str">"cmt">//coordinates number
class="kw">private: class="type">int    particlesNumber;  class=class="str">"cmt">//particles number
class="kw">private: class="type">class="kw">double envConstant;      class=class="str">"cmt">//environmental constant
class="kw">private: class="type">class="kw">double movConstant;      class=class="str">"cmt">//movement step
class="kw">private: class="type">class="kw">double exponent;         class=class="str">"cmt">//exponent
class="kw">private: class="type">class="kw">double vect    [];       class=class="str">"cmt">//vector
class="kw">private: class="type">bool  revision;
class="kw">private: class="type">class="kw">double 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);
class="kw">private: class="type">class="kw">double RNDfromCI(class="type">class="kw">double min, class="type">class="kw">double max);
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_EM::Init(class="kw">const class="type">int     coordinatesNumberP, class=class="str">"cmt">//coordinates number
                    class="kw">const class="type">int     particlesNumberP,   class=class="str">"cmt">//particles number
                    class="kw">const class="type">class="kw">double  envConstantP,       class=class="str">"cmt">//environmental constant
                    class="kw">const class="type">class="kw">double  movConstantP,       class=class="str">"cmt">//movement step
                    class="kw">const class="type">class="kw">double  exponentP)          class=class="str">"cmt">//exponent
{
  MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator
  fB       = -DBL_MAX;
  revision = class="kw">false;
  coordinatesNumber = coordinatesNumberP;
  particlesNumber   = particlesNumberP;
  envConstant       = envConstantP;
  movConstant       = movConstantP;
  exponent          = exponentP;
  ArrayResize(rangeMax,  coordinatesNumber);
  ArrayResize(rangeMin,  coordinatesNumber);
  ArrayResize(rangeStep, coordinatesNumber);
  ArrayResize(vect,      coordinatesNumber);
  ArrayResize(p,  particlesNumber);
  for (class="type">int i = class="num">0; i < particlesNumber; i++)
  {
    ArrayResize(p [i].c,  coordinatesNumber);

常见问题

每个解被当作带电荷的粒子,电荷量由适应度决定,优解吸引劣解、劣解被推离,按库仑力公式迭代移动位置来逼近最优。
种群规模太小容易早熟收敛,建议先按参数维度×20 撒点,边界外的解直接拉回并降权,避免力学计算溢出。
小布可在对应品种页直接调用内置的 EM 优化器,你填参数范围就能自动出较优解,不用自己搭代码。
连续空间、目标函数平滑时 EM 收敛更快;若解空间多尖峰离散,遗传算法的交叉更稳,优先选遗传。
经验上引力常数取 1、力截断半径设为搜索域对角线一半,步长再乘 0.3~0.7 衰减因子即可防震荡。