种群优化算法:人工多社区搜索对象(MSO)·进阶篇
(2/3)· 当社群不再自由漂游而是跨区跳跃,算法搜索效率的边界在哪里?
接上篇对自由移动社群的演变讨论,这一篇把假设改了:群在地区间跳迁,每个群带中枢且拥有记忆。多数人在套用种群优化时仍把社群当无锚点粒子,忽略了区域元数据与历史迁徙的复用价值,导致迭代浪费在重复探索上。
◍ 多群体优化器的私有结构与初始化入口
这套多群体扇区优化器(MSO)把搜索空间拆成扇区与群体两层,类内部用一组 private 字段记录维度、种群规模与各类抽样概率。coords 存坐标维度数量,popSize 是总种群规模,sectNumb 与 sectorsNumber 都指向扇区数(原文重复声明,实盘里建议合并避免歧义)。 probRNSsector、probUniformSector、probClgroup 三个 double 分别控制「随机选扇区」「扇区内均匀撒点」「按群体结果细化」的触发概率,power 则用于幂分布变形采样。外汇与贵金属行情噪声大,这类概率参数若直接套用默认值,过拟合历史 K 线的概率偏高,建议先在 EURUSD 的 M15 上跑蒙特卡洛敏感性。 下面这段是类内成员与 Init 方法签名,能看到扇区边界用 min_max_Sector[] 存,群体用 gr[] 管理。Init 的入参顺序即调参顺序:先给坐标数与种群规模,再给群体数、扇区数,最后补三个概率。复制进 MT5 头文件即可编译验证。
class="kw">private: class="type">int coords; class=class="str">"cmt">//coordinates number class="kw">private: class="type">int popSize; class=class="str">"cmt">//population size class="kw">private: class="type">int sectNumb; class=class="str">"cmt">//sectors number class="kw">private: class="type">class="kw">double sectorSpace []; class=class="str">"cmt">//sector space class="kw">private: S_Group gr []; class=class="str">"cmt">//groups class="kw">private: S_Min_Max min_max_Sector []; class=class="str">"cmt">//sector boundary by coordinates class="kw">private: class="type">int groups; class=class="str">"cmt">//number of groups class="kw">private: class="type">int sectorsNumber; class=class="str">"cmt">//sectors number class="kw">private: class="type">class="kw">double probRNSsector; class=class="str">"cmt">//probability random sector class="kw">private: class="type">class="kw">double probUniformSector; class=class="str">"cmt">//probability uniform distribution class="kw">private: class="type">class="kw">double probClgroup; class=class="str">"cmt">//probability of clarifying the group&class="macro">#x27;s result class="kw">private: class="type">class="kw">double power; class=class="str">"cmt">//power 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="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="kw">private: class="type">class="kw">double PowerDistribution(const class="type">class="kw">double In, const class="type">class="kw">double outMin, const class="type">class="kw">double outMax, const class="type">class="kw">double power); }; class="type">void C_AO_MSO::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
多群体初始化时怎么把粒子塞进各个群
这段构造函数负责把一个多群智能优化器(MSO)的初始状态搭起来。先用 MathSrand 按微秒计数重置随机数发生器,避免每次回测得到同一串伪随机轨迹。 参数里 populationSizeP 是总粒子数,groupsP 是群体数,两者相除得到每群基础粒子数 particles = popSize / groups。若不能整除,lost 个剩余粒子会沿 partInSwarms 数组逐个群 +1 轮转分配,保证总数恒定等于 popSize。 注意原文有一处明显的赋值覆盖:probUniformSector 先被赋为 probUniformSectorP,紧接着又被赋成 probClgroupP,后者覆盖了前者。在 MT5 里直接编译这段代码,probUniformSector 实际只保留澄清组结果的概率,调参时别被前面的形参名误导。 Moving 函数里按 coords 维度和 sectNumb 扇区数切分搜索空间:sectorSpace[c] = (rangeMax[c] - rangeMin[c]) / sectNumb,并给每个维度的每个扇区算好 min/max 边界。外汇与贵金属参数优化属高风险操作,回测拟合优度不代表实盘概率。
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 { MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator fB = -DBL_MAX; revision = false; coords = coordinatesNumberP; popSize = populationSizeP; groups = groupsP; sectNumb = sectorsNumberP; probRNSsector = probRNSsectorP; probUniformSector = probUniformSectorP; probUniformSector = probClgroupP; power = powerP; class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">int partInSwarms []; ArrayResize(partInSwarms, groups); class="type">int particles = popSize / groups; ArrayInitialize(partInSwarms, particles); class="type">int lost = popSize - particles * groups; if (lost > class="num">0) { class="type">int pos = class="num">0; while (true) { partInSwarms [pos]++; lost--; pos++; if (pos >= groups) pos = class="num">0; if (lost == class="num">0) break; } } class=class="str">"cmt">//---------------------------------------------------------------------------- ArrayResize(rangeMax, coords); ArrayResize(rangeMin, coords); ArrayResize(rangeStep, coords); ArrayResize(cB, coords); ArrayResize(gr, groups); for (class="type">int s = class="num">0; s < groups; s++) gr [s].Init(coords, partInSwarms [s], sectNumb); ArrayResize(sectorSpace, coords); ArrayResize(a, popSize); for (class="type">int i = class="num">0; i < popSize; i++) a [i].Init(coords); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_MSO::Moving() { if (!revision) { class=class="str">"cmt">//marking up sectors-------------------------------------------------------- ArrayResize(min_max_Sector, coords); for (class="type">int c = class="num">0; c < coords; c++) { sectorSpace [c] = (rangeMax [c] - rangeMin [c]) / sectNumb; min_max_Sector [c].Init(sectNumb); for (class="type">int sec = class="num">0; sec < sectNumb; sec++) { min_max_Sector [c].min [sec] = rangeMin [c] + sectorSpace [c] * sec; min_max_Sector [c].max [sec] = min_max_Sector [c].min [sec] + sectorSpace [c]; } } class=class="str">"cmt">//-------------------------------------------------------------------------- class="type">int sect = class="num">0; class=class="str">"cmt">//sector class="type">class="kw">double sectMin = class="num">0.0; class=class="str">"cmt">//sector&class="macro">#x27;s min
「粒子群初始化与全局最优回写」
这段逻辑干两件事:先把多组粒子随机撒进各自扇区,再把跑完的代理结果回灌给粒子并更新全局最优。外汇与贵金属参数空间里,这种随机播种方式可能让前几代收敛路径差异极大,属于高风险优化过程,建议先在历史数据上观察散布是否覆盖关键区间。 初始化时外层循环按 groups 数量建组,每组内先用 RNDfromCI 抽 sectNumb 范围内的扇区索引,越界就钳到末位;随后对组内每个粒子,在每个坐标维度上用扇区 min/max 做均匀随机,再经 SeInDiSp 离散化到网格步长。这样每组粒子天然被限制在若干随机扇区,而不是全空间盲搜。 Revision 函数负责回收:遍历 popSize 个代理,若某代理适应度 a[i].f 大于当前全局最优 fB,就刷新 fB 与 cB;接着按组把代理适应度写回对应粒子,并统计每组当前最优 gr[s].fB(初值 -DBL_MAX)。这一步是迭代闭环的关键,漏掉会导致群体失去方向。 直接把下面代码贴进 MT5 的 MSO 类实现里,改 groups 和 sectNumb 两个量就能看散布密度变化。
class="type">class="kw">double sectMax = class="num">0.0; class=class="str">"cmt">//sector&class="macro">#x27;s max class="type">int ind = class="num">0; class=class="str">"cmt">//index class="type">class="kw">double cd = class="num">0.0; class=class="str">"cmt">//coordinate for (class="type">int s = class="num">0; s < groups; s++) { class=class="str">"cmt">//select random sectors for the group------------------------------------- for (class="type">int c = class="num">0; c < coords; c++) { ind = (class="type">int)(RNDfromCI(class="num">0, sectNumb)); if (ind >= sectNumb) ind = sectNumb - class="num">1; gr [s].secInd [c] = ind; gr [s].secIndLast [c] = ind; } class=class="str">"cmt">//random distribute the particles of the group within the sectors--------- for (class="type">int p = class="num">0; p < ArraySize(gr [s].p); p++) { for (class="type">int c = class="num">0; c < coords; c++) { sect = gr [s].secInd [c]; cd = RNDfromCI(min_max_Sector [c].min [sect], min_max_Sector [c].max [sect]); gr [s].p [p].c [c] = SeInDiSp(cd, rangeMin [c], rangeMax [c], rangeStep [c]); } } } class=class="str">"cmt">//-------------------------------------------------------------------------- class=class="str">"cmt">//send particles to agents class="type">int cnt = class="num">0; for (class="type">int s = class="num">0; s < groups; s++) { for (class="type">int p = class="num">0; p < ArraySize(gr [s].p); p++) { ArrayCopy(a [cnt].c, gr [s].p [p].c, class="num">0, class="num">0, WHOLE_ARRAY); cnt++; } } revision = true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_MSO::Revision() { class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">//Update the best global solution for (class="type">int i = class="num">0; i < popSize; i++) { if (a [i].f > fB) { fB = a [i].f; ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } } class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">//Transfer the results from the agents to the particles class=class="str">"cmt">//and get the value of the best particle in the group at the current iteration class="type">int cnt = class="num">0; for (class="type">int s = class="num">0; s < groups; s++) { gr [s].fB = -DBL_MAX; for (class="type">int p = class="num">0; p < ArraySize(gr [s].p); p++) { gr [s].p [p].f = a [cnt].f; if (a [cnt].f > gr [s].fB) { gr [s].fB = a [cnt].f;
◍ 群体最优解与邻域扰动的交替更新
这段逻辑干两件事:先把每组跑出来的历史最优解固化下来,再基于概率做跨组借鉴或扇区随机重置。注意 if (gr [s].fB > gr [s].fBLast) 只在适应度刷新时才拷贝 cBLast 和 secIndLast,否则沿用上一代——这意味着落败的组不会污染已存的最优中心。
跨组借鉴的触发线是 RNDfromCI(0.0,1.0) < 0.6,即约 60% 的坐标维度会去挑一个适应度更高的其他组直接抄它的扇区索引与中心值;若随机抽到的就是自己这组,代码用 ind++ 再越界回卷到 0 来强制错开。
剩下 40% 走 else 分支,里面再按 probRNSsector 概率做扇区随机:从 sectNumb 个扇区里抽一个,用 min_max_Sector[c].min[sect] 到 .max[sect] 的区间做均匀随机坐标,再经 SeInDiSp 离散化写回 centre。外汇与贵金属行情下这种随机重置可能让参数跳出局部劣解,但回测过拟合风险高,实盘前务必在 MT5 用历史数据跑多组对照。
最后一段三层循环 groups × ArraySize(gr[s].p) × coords 是逐参数、逐坐标的收尾遍历,为下一轮评估做准备;开 MT5 把 probRNSsector 从 0.1 调到 0.3 观察收敛节奏变化是最直接的验证手法。
ArrayCopy(gr [s].cB, a [cnt].c, class="num">0, class="num">0, WHOLE_ARRAY); } cnt++; } } class="type">int sector = class="num">0; class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">//Update the best solution for the group for (class="type">int s = class="num">0; s < groups; s++) { if (gr [s].fB > gr [s].fBLast) { gr [s].fBLast = gr [s].fB; ArrayCopy(gr [s].cBLast, gr [s].cB, class="num">0, class="num">0, WHOLE_ARRAY); ArrayCopy(gr [s].secIndLast, gr [s].secInd, class="num">0, class="num">0, WHOLE_ARRAY); } ArrayCopy(gr [s].centre, gr [s].cBLast); } class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">int sect = class="num">0; class=class="str">"cmt">//sector class="type">class="kw">double sectMin = class="num">0.0; class=class="str">"cmt">//sector&class="macro">#x27;s min class="type">class="kw">double sectMax = class="num">0.0; class=class="str">"cmt">//sector&class="macro">#x27;s max class="type">int ind = class="num">0; class=class="str">"cmt">//index class="type">class="kw">double cd = class="num">0.0; class=class="str">"cmt">//coordinate for (class="type">int s = class="num">0; s < groups; s++) { for (class="type">int c = class="num">0; c < coords; c++) { if (RNDfromCI(class="num">0.0, class="num">1.0) < class="num">0.6) { ind = (class="type">int)(RNDfromCI(class="num">0, groups)); if (ind >= groups) ind = groups - class="num">1; if (ind == s) ind++; if (ind > groups - class="num">1) ind = class="num">0; if (gr [ind].fBLast > gr [s].fBLast) { gr [s].secInd [c] = gr [ind].secIndLast [c]; gr [s].centre [c] = gr [ind].cBLast [c]; } } else { if (RNDfromCI(class="num">0.0, class="num">1.0) < probRNSsector) { ind = (class="type">int)(RNDfromCI(class="num">0, sectNumb)); if (ind >= sectNumb) ind = sectNumb - class="num">1; gr [s].secInd [c] = ind; sect = gr [s].secInd [c]; cd = RNDfromCI(min_max_Sector [c].min [sect], min_max_Sector [c].max [sect]); gr [s].centre [c] = SeInDiSp(cd, rangeMin [c], rangeMax [c], rangeStep [c]); } else gr [s].secInd [c] = gr [s].secIndLast [c]; } } } class=class="str">"cmt">//---------------------------------------------------------------------------- for (class="type">int s = class="num">0; s < groups; s++) { for (class="type">int p = class="num">0; p < ArraySize(gr [s].p); p++) { for (class="type">int c = class="num">0; c < coords; c++)
粒子与组的记忆结构怎么落地
多组群优化(MSO)里,每个粒子和每个组都要各自记一套「历史最优坐标」,否则迭代会丢失局部收敛信息。下面这段结构定义把记忆拆成两个数组:cB 存坐标,fB 存该坐标对应的适应度,初始化时 fB 全部填 -DBL_MAX,意味着第一轮任何真实评价值都能覆盖它。 S_Memory 的 Init 按扇区数 sectNumb 动态扩容,ArrayResize 给 cB、fB 各开 sectNumb 长度,ArrayInitialize 把 fB 刷成负双精度最大值。扇区数量若设成 5,那 cB/fB 就是 5 个元素的一维数组,对应 5 个优化维度各自的记忆。 粒子层用 S_Particle 挂一个 pMemory[],组层用 S_Group 挂 sMemory[],两者大小都等于坐标数。Revision() 里先扫一遍 popSize 个 agent,只要 a[i].f 大于全局 fB 就刷新 cB 与 fB;随后按 groups 和组内粒子数把 a[cnt] 的适应度回填给 gr[s].p[p].f,并顺手把 gr[s].fB 预置为 -DBL_MAX 准备挑本组最优。 在 MT5 里把这套结构抄进 EA 的 include 头文件,改 sectNumb 参数就能控制记忆维度;外汇与贵金属波动大、滑点随机,实盘前务必用历史数据回测验证收敛稳定性,策略失效概率不低。
class="kw">struct S_Memory { class="type">void Init(class="type">int sectNumb) { ArrayResize(cB, sectNumb); ArrayResize(fB, sectNumb); ArrayInitialize(fB, -DBL_MAX); } class="type">class="kw">double cB []; class=class="str">"cmt">//the best sector coordinate, size is the number of sectors class="type">class="kw">double fB []; class=class="str">"cmt">//FF is the best coordinate on a sector, size is the number of sectors }; class="kw">struct S_Particle { <..............code is hidden.................> S_Memory pMemory []; class=class="str">"cmt">//particle memory, size - the number of coordinates }; class="kw">struct S_Group { <..............code is hidden.................> S_Memory sMemory []; class=class="str">"cmt">//group memory, size - number of coordinates }; class="type">void C_AO_MSO::Revision() { for (class="type">int i = class="num">0; i < popSize; i++) { if (a [i].f > fB) { fB = a [i].f; ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } } class="type">int cnt = class="num">0; for (class="type">int s = class="num">0; s < groups; s++) { gr [s].fB = -DBL_MAX; for (class="type">int p = class="num">0; p < ArraySize(gr [s].p); p++) { gr [s].p [p].f = a [cnt].f; if (a [cnt].f > gr [s].fB) {