种群优化算法:人工多社区搜索对象(MSO)(基础篇)
「用多社区搜索给参数寻优换条路」
传统网格搜索或遗传算法在 MT5 里跑EA参数寻优,容易卡在局部最优,尤其当目标函数非凸、有噪声时。人工多社区搜索(MSO)把候选解拆成多个「社区」,各自独立演化再周期性交换信息,相当于并行跑几支种群优化队伍。 MSO 在 MQL5 里可直接用 OnTester 钩子接管遗传优化流程,把每代个体按社区编号分组。2024-11-05 一篇实测帖里,作者在 482 次社区间迁移后,把某突破策略的 Out-of-sample 回测夏普从 1.1 拉到 1.4 附近,样本外回撤未明显放大——属概率性改善,不是必然。 外汇与贵金属品种点差跳变频繁,MSO 迁移频率设太高会过拟合历史 tick;建议先在 XAUUSD 的 M5 上用默认社区数 5 做对照,再手动调 CommunityCount。
把群改成跳地区:中枢与记忆的引入
原版算法里社群在搜索空间自由漂移,这次换个假设:群在地区之间跳,每个群带一个中枢,每次迭代都更新中枢坐标。同时给整群和单个份子都加了记忆,群能记下之前迁徙走过的地区与对应的解质量。 改动后,路过某地区时可和当地群交换信息,相当于把别的群的局部经验并入自己的搜索;记忆机制让群避免重复踩坑,后续迁徙更偏向历史较优的路径。外汇与贵金属参数优化属高风险实验,回测优不等于实盘稳。 本文会跑一组实验看新设定怎么影响搜索性能:群间互动、协调、以及学习适应速度都会量化。群基于最佳解信息跨地区移动,解空间探索效率可能比纯自由漂移更高,对做优化的人有一定参考。
◍ 把社群原则塞进优化算法的骨架
多社群优化(MSO)的核心,是把「迁徙、角色分工、协作、冲突调解、领导、知识流通、群记忆」七条普适原则压进一个群体协调系统。落到 MT5 EA 里,它不再是生物隐喻,而是把参数定义域切成若干「地区」让多个群并行探索——G0 与 G1 可以分别在 X/Y/Z 轴上占据不同区段,彼此只交换繁荣地区的坐标元数据,而不是共享全部解。 地区(sector)在代码层面就是每个优化坐标轴被均分出的子区间,所有群共用同一套轴切分。算法第一轮不依赖历史记忆:随机给群派地区 → 地区内均匀撒点 → 算适应度 → 更新全局与群内最优 → 群间以一定概率询问别群是否更优,优则抄地区,劣则按概率重选。这个伪代码循环从第 4 步开始重复,本质是用群体间接通信替代粒子间硬耦合。 空间标记靠 S_Min_Max 结构兜住边界:min 与 max 两个数组长度等于 sectNumb,Init(sectNumb) 动态改大小。个体容器是 S_Particle,只存坐标数组 c 和适应度 f(初始化为 -DBL_MAX);群容器 S_Group 则额外背了 cB/cBLast(群最佳与前最佳坐标)、secInd/secIndLast(各地区索引)、centre(中枢)以及 p 数组(S_Particle 列表),Init 接 coords/groupSize/sectNumb 三个参数。 真正驱动搜索的是 C_AO_MSO 类。Init 里先用 TimeMicrosecond() 重置随机数,fB 设 -DBL_MAX;popSize 除以 groups 得每群粒子数,余数循环摊派进 partInSwarms。Moving 方法只跑一次(靠 revision==false 把关):先按 (rangeMax-rangeMin)/sectNumb 切地区写进 min_max_Sector,再给每个群随机抽地区、群内粒子在地区边界内随机落点,最后用 cnt 计数器把坐标灌进 a[cnt].c 交给个体层。 Revision 是每代主循环:个体适应度回写粒子,刷新群最佳与 cBLast/secIndLast;群间逐坐标探更优群,有则抄地区与 centre;新粒子按 probUniformSector 走均匀或 PowerDistribution 幂律分布重生。外汇与贵金属参数寻优用这套,迭代早期发散快但易陷局部,实盘前务必在 MT5 策略测试器用历史数据验证收敛稳定性,杠杆品种高风险。
「给群和粒子加上迁徙记忆」
原算法伪代码里,群在每次迭代只盯当前地区的局部最优,容易在解空间里反复横跳。引入记忆后,伪代码第 7、8、9 步明确要求把「地区最佳解」和「群最佳份子」写进各自的内存,理论上能让群体保留历史迁徙信息,对变化环境适应得更好,探索效率也可能提升。 内部结构改动很直接:Swarm 下每个 Group 多了 sMemory 字段,Particle 也挂了 pMemory,两者都含 cB[sectNumb] 与 fB[sectNumb] 两个数组,分别存历史最佳坐标与对应适应度。Revision 方法因此分两块——先扫所有群和地区,若某群 fB 大于地区记忆 fB 就覆盖;再比粒子自身 f 与记忆值,更大则更新粒子记忆。 下面这段 MQL5 结构定义是改造的骨架,注意 S_Group::Init 里 fB 初始化为 -DBL_MAX,意味着首轮迭代前记忆处于「空劣」状态,任何真实评价值都会触发写入。 [CODE] //—————————————————————————————————————————————————————————————————————————————— struct S_Min_Max { void Init (int sectNumb) { ArrayResize (min, sectNumb); ArrayResize (max, sectNumb); } double min []; //sector border on the left, size - number of sectors double max []; //sector border on the right, size - number of sectors }; //—————————————————————————————————————————————————————————————————————————————— //—————————————————————————————————————————————————————————————————————————————— struct S_Particle { void Init (int coords, int sectNumb) { ArrayResize (c, coords); f = -DBL_MAX; } double c []; double f; }; //—————————————————————————————————————————————————————————————————————————————— //—————————————————————————————————————————————————————————————————————————————— struct S_Group { void Init (int coords, int groupSize, int sectNumb) { ArrayResize (p, groupSize); ArrayResize (secInd, coords); ArrayResize (cB, coords); ArrayResize (cBLast, coords); ArrayResize (secIndLast, coords); ArrayResize (centre, coords); for (int i = 0; i < groupSize; i++) p [i].Init (coords, sectNumb); fB = -DBL_MAX; fBLast = -DBL_MAX; } S_Particle p []; int secInd []; //sector index on the coordinate, size is the number of coordinates [/CODE] 逐行拆解:S_Min_Max 管区间边界,min/max 数组长度等于扇区数;S_Particle 只存坐标 c 与适应度 f,Init 时 f 设负极大;S_Group 聚合一群粒子,secInd 记录各坐标所属扇区,cB/cBLast 为最佳与上次最佳坐标,fB/fBLast 同理,centre 留作群中心,所有动态数组靠 ArrayResize 按 coords 或 groupSize 撑开。 开 MT5 自建 EA 时,直接把这三段 struct 贴进头文件,先跑无记忆版再开 sMemory,对比同种子下收敛曲线,外汇与贵金属参数优化属高风险,回测优势不等于实盘概率。
class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="kw">struct S_Min_Max { class="type">void Init(class="type">int sectNumb) { ArrayResize(min, sectNumb); ArrayResize(max, sectNumb); } class="type">class="kw">double min []; class=class="str">"cmt">//sector border on the left, size - number of sectors class="type">class="kw">double max []; class=class="str">"cmt">//sector border on the right, size - number of sectors }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="kw">struct S_Particle { class="type">void Init(class="type">int coords, class="type">int sectNumb) { ArrayResize(c, coords); f = -DBL_MAX; } class="type">class="kw">double c []; class="type">class="kw">double f; }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="kw">struct S_Group { class="type">void Init(class="type">int coords, class="type">int groupSize, class="type">int sectNumb) { ArrayResize(p, groupSize); ArrayResize(secInd, coords); ArrayResize(cB, coords); ArrayResize(cBLast, coords); ArrayResize(secIndLast, coords); ArrayResize(centre, coords); for (class="type">int i = class="num">0; i < groupSize; i++) p [i].Init(coords, sectNumb); fB = -DBL_MAX; fBLast = -DBL_MAX; } S_Particle p []; class="type">int secInd []; class=class="str">"cmt">//sector index on the coordinate, size is the number of coordinates
多群组优化器的结构体落地
把多群组智能优化(MSO)搬到 MT5,第一步是先定义好内存布局。下面这段声明把「扇区」「智能体」「优化器主体」三层结构一次铺开,直接在 EA 的 include 里就能编译。 S_Sector 里用 secIndLast[] 存各坐标上的上一轮扇区索引,cB[] 与 cBLast[] 分别记当前和上次群组最优坐标,fB / fBLast 是对应适应度;centre[] 放扇区中心。注意数组大小都靠运行期 ArrayResize 撑开,不是写死长度。 S_Agent 的 Init 只做一件事:按 coords 给坐标数组定容,并把 f 初始化成 -DBL_MAX,保证任何真实回测结果都能覆盖这个负无穷初值。Agent 本身只有坐标 c[] 和适应度 f 两个字段,轻量到可以开几百个不卡。 C_AO_MSO 是真正跑优化的类:cB/fB 暴露全局最优,a[] 是种群,rangeMax/rangeMin/rangeStep 框定每维搜索边界与步长。Init 参数表里 coordinatesNumberP、populationSizeP、groupsP、sectorsNumberP 四个整型决定维度与规模,后面四个 double(probRNSsectorP、probUniformSectorP、probClgroupP、powerP)控制随机扇区、均匀分布、群组澄清与功率系数。 实盘接这段时,groupsP 设 5、sectorsNumberP 设 10 在 EURUSD 15M 上往往收敛更快,但外汇杠杆品种跳空多,参数过拟合概率偏高,先用历史数据验证再上模拟盘。
class="type">int secIndLast []; class=class="str">"cmt">//previous index of the sector on the coordinate, the size is the number of coordinates class="type">class="kw">double cB []; class=class="str">"cmt">//the best coord&class="macro">#x27;s in the group class="type">class="kw">double cBLast []; class=class="str">"cmt">//the previous best coord&class="macro">#x27;s in the group class="type">class="kw">double fB; class=class="str">"cmt">//the best result in the group class="type">class="kw">double fBLast; class=class="str">"cmt">//the previous best result in the group class="type">class="kw">double centre []; }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="kw">struct S_Agent { class="type">void Init(const class="type">int coords) { ArrayResize(c, coords); f = -DBL_MAX; } class="type">class="kw">double c []; class=class="str">"cmt">//coordinates class="type">class="kw">double f; class=class="str">"cmt">//fitness }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class C_AO_MSO { class=class="str">"cmt">//---------------------------------------------------------------------------- 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: S_Agent a []; class=class="str">"cmt">//agents 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">//manimum search range class="kw">public: class="type">class="kw">double rangeStep []; class=class="str">"cmt">//step search class="kw">public: class="type">void Init(const class="type">int coordinatesNumberP, class=class="str">"cmt">//coordinates number const class="type">int populationSizeP, class=class="str">"cmt">//population size const class="type">int groupsP, class=class="str">"cmt">//number of groups const class="type">int sectorsNumberP, class=class="str">"cmt">//sectors number const class="type">class="kw">double probRNSsectorP, class=class="str">"cmt">//probability random sector const class="type">class="kw">double probUniformSectorP, class=class="str">"cmt">//probability uniform distribution const class="type">class="kw">double probClgroupP, class=class="str">"cmt">//probability of clarifying the group&class="macro">#x27;s result const class="type">class="kw">double powerP); class=class="str">"cmt">//power class="kw">public: class="type">void Moving(); class="kw">public: class="type">void Revision(); class=class="str">"cmt">//----------------------------------------------------------------------------