种群优化算法:引力搜索算法(GSA)·进阶篇
引力搜索类的初始化与内存排布
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 次,内存开销非线性但可控。外汇与贵金属市场高风险,参数改动仅影响计算规模,不预示任何收益。
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 脚本看变量变化。
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],能直接看到越界回卷触发频率和外汇符号参数寻优稳定性的差异,贵金属与外汇品种均属高风险,结果仅代表概率倾向。
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 属于高风险尝试,大概率跑不赢现代群智能。它的唯一硬优点是代理之间全互联、不依赖已找出的全局最优,算是个有趣的物理隐喻多于实用工具。
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 策略测试器里拿历史数据跑一遍,看清收敛曲线和过拟合缺口,再谈要不要接进自己的交易系统。工具用熟了,它才是工具。