种群优化算法:引力搜索算法(GSA)·进阶篇
📘

种群优化算法:引力搜索算法(GSA)·进阶篇

第 2/2 篇

引力搜索类的初始化与内存排布

C_AO_GSA 的 Init 方法负责把外部传入的群体参数固化到类成员,并一次性开好所有动态数组。调用 MathSrand((int)GetMicrosecondCount()) 用微秒级时间戳重置随机数发生器,避免每次 EA 加载得到同一串伪随机序列。 fB 初始化为 -DBL_MAX,代表当前最优适应度尚未产生;revision 置 false,说明还没进入修订状态。coordinatesNumber、objectsNumber 以及引力常数 G_constant、a_constant、e_constant 等全部直接赋值保存,maxIterations 决定后续主循环的上限。 数组维度按坐标数与对象数展开:rangeMax/rangeMin/rangeStep 长度为 coordinatesNumber,o 长度为 objectsNumber。随后对每个对象 i 再细分 c(坐标)、v(速度)、R(与其他对象关系)、F(受力)四个数组,长度分别对应坐标数和对象数,o[i].f 同样预置为 -DBL_MAX。 最后 cB 按 coordinatesNumber Resize,留给全局最优坐标。开 MT5 把这段贴进类实现,改 objectsNumber 从 10 调到 30,能直接观察 ArrayResize 调用次数由约 4+10×4=44 次升到 4+30×4=124 次,内存开销非线性但可控。外汇与贵金属市场高风险,参数改动仅影响计算规模,不预示任何收益。

MQL5 / C++
class="kw">private: class="type">class="kw">double Scale(class="type">class="kw">double In, class="type">class="kw">double InMIN, class="type">class="kw">double InMAX, class="type">class="kw">double OutMIN, class="type">class="kw">double OutMAX, class="type">bool revers);
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_GSA::Init(class="kw">const class="type">int      coordinatesNumberP, class=class="str">"cmt">//coordinates number
                     class="kw">const class="type">int      objectsNumberP,     class=class="str">"cmt">//objects number
                     class="kw">const class="type">class="kw">double   PowerOfdistanceP,   class=class="str">"cmt">//power of distance
                     class="kw">const class="type">class="kw">double   GraviPert_MinP,     class=class="str">"cmt">//gravitational perturbation Min
                     class="kw">const class="type">class="kw">double   GraviPert_MaxP,     class=class="str">"cmt">//gravitational perturbation Min
                     class="kw">const class="type">class="kw">double   VelocityPert_MinP,  class=class="str">"cmt">//Velocity perturbation Min
                     class="kw">const class="type">class="kw">double   VelocityPert_MaxP,  class=class="str">"cmt">//Velocity perturbation Max
                     class="kw">const class="type">class="kw">double   G_constantP,        class=class="str">"cmt">//G constant
                     class="kw">const class="type">class="kw">double   a_constantP,        class=class="str">"cmt">//a constant
                     class="kw">const class="type">class="kw">double   e_constantP,        class=class="str">"cmt">//e constant
                     class="kw">const class="type">int      maxIterationsP)    class=class="str">"cmt">//max Iterations
{
  MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator
  fB       = -DBL_MAX;
  revision = class="kw">false;
  coordinatesNumber = coordinatesNumberP;
  objectsNumber     = objectsNumberP;
  PowerOfdistance   = PowerOfdistanceP;
  GraviPert_Min     = GraviPert_MinP;
  GraviPert_Max     = GraviPert_MaxP;
  VelocPert_Min     = VelocityPert_MinP;
  VelocPert_Max     = VelocityPert_MaxP;
  G_constant        = G_constantP;
  a_constant        = a_constantP;
  e_constant        = e_constantP;
  maxIterations     = maxIterationsP;
  ArrayResize(rangeMax,  coordinatesNumber);
  ArrayResize(rangeMin,  coordinatesNumber);
  ArrayResize(rangeStep, coordinatesNumber);
  ArrayResize(o,         objectsNumber);
  for (class="type">int i = class="num">0; i < objectsNumber; i++)
  {
    ArrayResize(o [i].c,  coordinatesNumber);
    ArrayResize(o [i].v,  coordinatesNumber);
    ArrayResize(o [i].R,  objectsNumber);
    ArrayResize(o [i].F,  coordinatesNumber);
    o [i].f  = -DBL_MAX;
  }
  ArrayResize(cB, coordinatesNumber);
}

「引力搜索里的初始化与受力计算」

这段逻辑实现的是基于引力搜索的群体优化内核,首次运行(revision 为 false)时把所有对象坐标随机撒在给定区间,并把适应度 f 预设为 -DBL_MAX,质量 M 清 0。 之后每轮先扫一遍所有对象,找出最小与最大适应度 Fmin、Fmax,再按 (f-Fmin)/(Fmax-Fmin) 归一化到 0.1~1.0 的质量区间;若全体适应度相等则质量直接取 1.0。 对象间欧氏距离用对称缓存避免重复算:R[obj2] 一旦算出就同时写回 R[obj][obj2],复杂度从 N^2*C 降一半。 引力系数 G 随迭代衰减:G = G_constant * exp(-a_constant * (iter/maxIterations)),迭代越深引力越弱,对象收敛可能更平稳。外汇与贵金属市场高波动,这类优化仅作参数寻优辅助,实盘须自行验证。 下面这段是原文核心循环,可直接粘进 MT5 脚本看变量变化。

MQL5 / C++
if (!revision)
{
fB = -DBL_MAX;
  for (class="type">int obj = class="num">0; obj < objectsNumber; obj++)
  {
    for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
    {
      o [obj].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]);
      o [obj].c [c] = SeInDiSp(o [obj].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      o [obj].v [c] = class="num">0.0;
      o [obj].M     = class="num">0.0;
      o [obj].f     = -DBL_MAX;
    }
  }
  revision = true;
}
class=class="str">"cmt">//find the minimum and maximum fitness
for (class="type">int obj = class="num">0; obj < objectsNumber; obj++)
{
  if (o [obj].f < Fmin) Fmin = o [obj].f;
  if (o [obj].f > Fmax) Fmax = o [obj].f;
}
class=class="str">"cmt">//calculating the mass of objects
for (class="type">int obj = class="num">0; obj < objectsNumber; obj++)
{
  Fo = o [obj].f;
  if (Fmax == Fmin) Mo = class="num">1.0;
  else Mo = (Fo - Fmin) / (Fmax - Fmin);
  o [obj].M = Scale(Mo, class="num">0.0, class="num">1.0, class="num">0.1, class="num">1.0, class="kw">false);
}
class=class="str">"cmt">//calculation of Euclidean distances between all objects
for (class="type">int obj = class="num">0; obj < objectsNumber; obj++) ArrayInitialize(o [obj].R, class="num">0.0);
for (class="type">int obj = class="num">0; obj < objectsNumber; obj++)
{
  for (class="type">int obj2 = class="num">0; obj2 < objectsNumber; obj2++)
  {
    if (obj != obj2)
    {
      if (o [obj].R [obj2] == class="num">0.0)
      {
        for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
        {
          diffDist = o [obj].c [c] - o [obj2].c [c];
          o [obj].R [obj2] += diffDist * diffDist;
        }
        o [obj].R [obj2] = sqrt(o [obj].R [obj2]);
        o [obj2].R [obj] = o [obj].R [obj2];
      }
    }
  }
}
class=class="str">"cmt">//calculate the force vector for each object
for (class="type">int obj = class="num">0; obj < objectsNumber; obj++) ArrayInitialize(o [obj].F, class="num">0.0);
class="type">class="kw">double G = G_constant * exp(-a_constant * (iter / maxIterations));
for (class="type">int obj = class="num">0; obj < objectsNumber; obj++)
{
  for (class="type">int obj2 = class="num">0; obj2 < objectsNumber; obj2++)
  {
    if (obj != obj2)
    {
      for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
      {
        diffDist = o [obj2].c [c] - o [obj].c [c];
        if (o [obj].R [obj2] != class="num">0.0)
        {
          o [obj] .F [c] += G * o [obj].M * o [obj2].M * diffDist / (pow(o [obj].R [obj2], PowerOfdistance) + e_constant);
        }
      }
    }
  }
}
class=class="str">"cmt">//calculation of acceleration and velocity for all objects
class="type">class="kw">double a = class="num">0.0; class=class="str">"cmt">//acceleration
for (class="type">int obj = class="num">0; obj < objectsNumber; obj++)
{
  for (class="type">int c = class="num">0; c < coordinatesNumber; c++)
  {
    r = RNDfromCI(GraviPert_Min, GraviPert_Max);
    a = o [obj].F [c] * r / o [obj].M;

◍ 引力扰动后的越界回卷与最优解捕获

上面这段逻辑干了两件事:先用随机扰动系数 r 改写每个粒子的速度并推进坐标,再把跑出边界的坐标做环形回卷(c 超过 rangeMax 就跳到 rangeMin,反之跳回 rangeMax),最后用 SeInDiSp 按步长对齐到离散网格。 随机系数由 RNDfromCI(GraviPert_Min, GraviPert_Max) 在区间内抽取,意味着每次迭代的扰动幅度不是固定值,而是随参数区间宽度变化;若你把 GraviPert_Max 设得远大于 Min,粒子跳跃会更剧烈,收敛路径更发散。 Revision 函数负责在全部 objectsNumber 个粒子里捞最优:只要某个粒子的适应度 o[s].f 大于当前记录 fB,就刷新 fB 并把该粒子坐标数组 cB 整组拷贝出来。 开 MT5 把这段贴进 EA 的种群迭代循环,故意把 GraviPert 区间拉到 [0.8, 2.5] 对比 [0.1, 0.3],能直接看到越界回卷触发频率和外汇符号参数寻优稳定性的差异,贵金属与外汇品种均属高风险,结果仅代表概率倾向。

MQL5 / C++
  r = RNDfromCI(GraviPert_Min, GraviPert_Max);
  o [obj].v [c] = o [obj].v [c] * r + a;
  o [obj].c [c] = o [obj].c [c] + o [obj].v [c];
  if (o [obj].c [c] > rangeMax [c]) o [obj].c [c] = rangeMin [c];
  if (o [obj].c [c] < rangeMin [c]) o [obj].c [c] = rangeMax [c];
  o [obj].c [c] = SeInDiSp(o [obj].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_GSA::Revision()
{
  for (class="type">int s = class="num">0; s < objectsNumber; s++)
  {
    if (o [s].f > fB)
    {
      fB = o [s].f;
      ArrayCopy(cB, o [s].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

引力搜索在基准函数上的真实战绩

把 GSA 接到 EURUSD M1 的测试台上跑了一遍,参数定为 PowerOfdistance=2.0、GraviPert 0.2~0.6、VelocityPert 0~1.0、G=2.0、a=20.0、e=0.01。Rastrigin 函数 5/25/500 维的 Score 分别是 0.91252 / 0.73640 / 0.46527;Forest 函数同维度掉到 0.41941 / 0.26526 / 0.06439;Megacity 离散函数更惨,500 维只剩 0.01950。 维度一高,GSA 的收敛质量就崩。横向对比 12 个算法在 8 组测试里的综合分,GSA 拿 52.786 排第 7,落后于 IWO(100)、ACOm(96.8)、COAm(74.4) 等,仅略强于 FSS、PSO、RND 和 GWO。它在 10 变量平滑 Rastrigin 上还能看,但 1000 参数版本直接吃 0 分。 引力摄动和速度摄动区间如果都设成 1.0,扰动就关了,粒子只会按纯引力漂移;G_constant 拉高会加快速度变化,a_constant 则在迭代里收缩搜索半径做细化。想复现的话,开 MT5 把附录的 Test_AO_GSA 挂上 EURUSD M1,照上面那组输入跑 10000 次函数评价即可。 这算法对梯度存在与否很敏感,维度上去后扩展性极差,外汇与贵金属优化里拿它调大量参数的 EA 属于高风险尝试,大概率跑不赢现代群智能。它的唯一硬优点是代理之间全互联、不依赖已找出的全局最优,算是个有趣的物理隐喻多于实用工具。

MQL5 / C++
input class="type">class="kw">double PowerOfdistance_P  = class="num">2.0;   class=class="str">"cmt">//Power of distance
input class="type">class="kw">double GraviPert_Min_P    = class="num">0.2;   class=class="str">"cmt">//Gravitational perturbation Min
input class="type">class="kw">double GraviPert_Max_P    = class="num">0.6;   class=class="str">"cmt">//Gravitational perturbation Max
input class="type">class="kw">double VelocityPert_Min_P = class="num">0.0;   class=class="str">"cmt">//Velocity perturbation Min
input class="type">class="kw">double VelocityPert_Max_P = class="num">1.0;   class=class="str">"cmt">//Velocity perturbation Max
input class="type">class="kw">double G_constant_P       = class="num">2.0;   class=class="str">"cmt">//G constant
input class="type">class="kw">double a_constant_P       = class="num">20.0;  class=class="str">"cmt">//a constant
input class="type">class="kw">double e_constant_P       = class="num">0.01;  class=class="str">"cmt">//e constant

「把工具请下神坛」

这位从 2007 年就开始做机器学习与优化算法的作者,把 BSA、DEA、ES、BBO、DOS、CA 等一整套种群优化算法都开源在了 GitHub 的 MQL5 仓库里,还建了 Telegram 群组供免费测试。注意他提醒过冒牌号频出,真实昵称只有 @JQS_aka_Joo 一个。 这些算法本质上是给 EA 参数寻优用的数学搜索器,不是印钞机。外汇和贵金属杠杆高、滑点跳空频繁,直接把优化结果当实盘信号,回测漂亮也可能在 live 里被摩擦到怀疑人生。 真要上手,先去仓库拉一份 CA 或 BBO 的源码,在 MT5 策略测试器里拿历史数据跑一遍,看清收敛曲线和过拟合缺口,再谈要不要接进自己的交易系统。工具用熟了,它才是工具。

常见问题

用连续数组按代理索引存放位置和速度,避免零散结构体;初始化时均匀撒点并清零速度,方便后续批量更新受力。
合力来自其他代理的引力加权求和,权重含距离与质量;质量小的代理受力弱但仍参与运动,不会被直接丢弃。
小布可替你加载基准函数、自动初始化种群并在越界时回卷捕获最优解,你只需看结果分布。
采用越界回卷(反射或取模)把代理拉回搜索域,同时每代记录历史最佳,防止优质解溢出丢失。
在低维单峰函数收敛快,高维多峰易早熟;外汇贵金属高风险,建议先离线回测再考虑轻量寻参。