种群优化算法:进化策略,(μ,λ)-ES 和 (μ+λ)-ES·进阶篇
进化种群的初始化与游走逻辑
这段 C_AO_POES 类的构造函数把遗传算法的核心容器一次性拉起:用 MathSrand 以微秒计数重置随机种子,避免每次回测得到同一串伪随机序列。fB 置为 -DBL_MAX 代表当前最优适应度尚未产生,revision 置 false 意味着种群还没做过首轮随机散布。 数组 ind、val、pTemp、a 全部按 popSize 扩容,parents 按 parentsP 扩容,rangeMax/Min/Step 与 cB 按坐标维度 coords 扩容。循环里对每个个体和父代调用 Init(coords),相当于把搜索空间维度写进对象内部状态。 Moving() 的首轮行为由 revision 控制:当其为 false 时,每个个体的每个坐标先 RNDfromCI 在 [rangeMin, rangeMax] 均匀抽样,再经 SeInDiSp 对齐到离散网格步长 rangeStep,完成后 revision 置 true 并直接返回——这是种群的冷启动。 后续调用进入真实进化:对每个个体每个坐标,随机挑一个父代索引(越界则钳到 parentsNumb-1),以该父代坐标为中心、以 (rangeMax-rangeMin)*mutationPower 为半径做截断,再用 GaussDistribution 按 sigmaM 做高斯扰动,最后同样对齐离散步长。mutationPower 与 sigmaM 直接决定探索半径,调小可能让收敛更稳但偏慢。 开 MT5 把这段塞进 EA 的优化框架,先只改 mutationPower 从 0.1 到 0.5 跑同一品种,能直观看到参数空间覆盖密度的变化。外汇与贵金属杠杆高,回测优解实盘可能失效,任何结论都只是概率倾向。
const class="type">int parentsP, class=class="str">"cmt">//number of parents, < Population size const class="type">class="kw">double mutationPowerP, class=class="str">"cmt">//mutation power const class="type">class="kw">double sigmaP) class=class="str">"cmt">//sigma { MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator fB = -DBL_MAX; revision = false; coords = coordsP; popSize = popSizeP; parentsNumb = parentsP; mutationPower = mutationPowerP; sigmaM = sigmaP; ArrayResize(ind, popSize); ArrayResize(val, popSize); ArrayResize(pTemp, popSize); ArrayResize(a, popSize); for (class="type">int i = class="num">0; i < popSize; i++) a [i].Init(coords); ArrayResize(parents, parentsNumb); for (class="type">int i = class="num">0; i < parentsNumb; i++) parents [i].Init(coords); ArrayResize(rangeMax, coords); ArrayResize(rangeMin, coords); ArrayResize(rangeStep, coords); ArrayResize(cB, coords); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_POES::Moving() { class=class="str">"cmt">//---------------------------------------------------------------------------- if (!revision) { for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { a [i].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]); a [i].c [c] = SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">int indx = class="num">0; class="type">class="kw">double min = class="num">0.0; class="type">class="kw">double max = class="num">0.0; class="type">class="kw">double dist = class="num">0.0; for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { indx = (class="type">int)RNDfromCI(class="num">0, parentsNumb); if (indx >= parentsNumb) indx = parentsNumb - class="num">1; a [i].c [c] = parents [indx].c [c]; dist = (rangeMax [c] - rangeMin [c]) * mutationPower; min = a [i].c [c] - dist; if (min < rangeMin [c]) min = rangeMin [c]; max = a [i].c [c] + dist; if (max > rangeMax [c]) max = rangeMax [c]; a [i].c [c] = GaussDistribution(a [i].c [c], min, max, sigmaM); a [i].c [c] = SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } }
「进化种群的代际更替逻辑」
这段 MT5 代码实现了一个基于适应度筛选的种群修订机制,核心在 Revision() 里:遍历当前种群 a[],若某个个体适应度 a[i].f 超过历史最优 fB,就记录下标并拷贝其坐标数组到 cB,随后对种群排序、取前 parentsNumb 个作为新一代父代。 S_Agent 结构体用 Init() 完成-agent 初始化,坐标数组按传入的 coords 维度动态扩维,f 初值设为 -DBL_MAX 代表未评估,yearsNumber 记录存活代数。外部数组 ind、val、pTemp、a、parents 均按 popSize+parentsNumb 扩维,确保交叉与淘汰不越界。 父代老化处理很直接:yearsNumber 自增后若超过 lifespan 阈值,适应度强制归为 -DBL_MAX,下一轮排序自然被淘汰;新种群 a[] 整体并入 parents 尾部再统一排序。开 MT5 把 lifespan 设小一点(如 5),能明显看到旧父代更快出局、参数搜索偏向近期优解。外汇与贵金属市场高波动,这类自适应寻优仅作策略参数探索,实盘仍可能失效。
class="type">void C_AO_POES::Revision() { class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">int indx = -class="num">1; for (class="type">int i = class="num">0; i < popSize; i++) { if (a [i].f > fB) indx = i; } if (indx != -class="num">1) { fB = a [indx].f; ArrayCopy(cB, a [indx].c, class="num">0, class="num">0, WHOLE_ARRAY); } class=class="str">"cmt">//---------------------------------------------------------------------------- Sorting(a, popSize); for (class="type">int i = class="num">0; i < parentsNumb; i++) { parents [i] = a [i]; } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="kw">struct S_Agent { class="type">void Init(class="type">int coords) { ArrayResize(c, coords); f = -DBL_MAX; yearsNumber = class="num">0; } class="type">class="kw">double c []; class=class="str">"cmt">//coordinates class="type">class="kw">double f; class=class="str">"cmt">//fitness class="type">int yearsNumber; }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— ArrayResize(ind, popSize + parentsNumb); ArrayResize(val, popSize + parentsNumb); ArrayResize(pTemp, popSize + parentsNumb); ArrayResize(a, popSize); for (class="type">int i = class="num">0; i < popSize; i++) a [i].Init(coords); ArrayResize(parents, popSize + parentsNumb); for (class="type">int i = class="num">0; i < popSize + parentsNumb; i++) parents [i].Init(coords); a [i].yearsNumber = class="num">1; class=class="str">"cmt">//---------------------------------------------------------------------------- for (class="type">int i = class="num">0; i < parentsNumb; i++) { parents [i].yearsNumber++; if (parents [i].yearsNumber > lifespan) { parents [i].f = - DBL_MAX; } } for (class="type">int i = parentsNumb; i < parentsNumb + popSize; i++) { parents [i] = a [i - parentsNumb]; } Sorting(parents, parentsNumb + popSize);
◍ 别用 Rastrigin 给自己算法贴金
早先大家习惯拿 Rastrigin 函数当优化算法的测试基准,但它严格的周期性和均值平衡的最小值/最大值,反而让某些算法靠“撒点”就能显得很强。当变量数拉到 1000 时,这种假象尤其明显——部分算法只是把个体均匀铺在表面,并没真在搜极值,却给出接近全局最优的结果。 更靠谱的做法是换用无明显形态、最小值最大值不平衡的函数,比如带噪声、非线性依赖或大量局部极值的类型。文中把测试函数分成“简单”和“复杂”两类:简单函数的表面积大多落在 max 与 min 中线之上,随机撒点都会让人误以为优化很成功。Rastrigin 就被归为这类,会夸大算法表现。 改造版 Rastrigin 被命名为 Hilly,它加进了多个“山丘”和“山谷”,全局最优从边界移到了内部,周期性那一段也不再能直接跳步到位。Hilly 与 Forest、Megacity 同属复杂函数,三者高度超过最大高度 50% 的表面积都约占总面积 20%,逼着算法真去爬坑。 测试方法也调了:重复运行从 5 轮提到 10 轮,用来压低随机尖峰。外汇与贵金属 EA 优化属高风险实验,这类复杂函数仅作算法压力参考,不代表实盘收益。
进化策略跑分与绝对收敛标尺
把测试函数改成返回值锁定在 0.0–1.0 区间后,结果读法就直白了:1.0 代表 100% 收敛到全局最大值,0.32527 即找到了 32.5% 的理论极值。9 项测试全满分为 9.0,任何算法“% of MAX”都不会超过 100%。 (μ+λ)-ES 这组参数 C_AO_(P_O)ES:100:150:0.02:8.0:10 拿下了 6.496 总分,相当于理论上限的 72.18%。单独看 Forest 函数,5/25/500 个峰位的收敛率分别是 1.0、0.93522、0.39179;作者专门跑了 20 轮复验,每一轮在复杂 Forest 上都是满血 1.0 收敛。 对照表里 (μ,λ)-ES 只有 4.610(51.22%),但彩色分布图上是均匀绿、无突变,说明稳定性好、不易踩坑。外汇与贵金属参数优化属高风险场景,这类离群收敛优势不代表实盘能复现,MT5 里用附带的评级脚本重跑前先小额验证。 完整 28 算法榜单里,SDSm 63.44%、SIA 60.76% 紧随其后,DE 55.06%,而传统 PSO 仅 24.77%、随机基线 22.37%。想自己比划,直接把测试函数绝对值化后塞进 MT5 脚本,换 λ 和变异步长 0.02 看分数跳动。
「别急着下结论」
把进化策略搬进 MT5 优化器,最实在的产出是(μ,λ)-ES 当前在积分榜领先次席 SDSm 约 10%,但这只说明在已测函数族里它更抗粘连、收敛更猛,不代表换到真实 tick 行情就稳。 两类变体外部参数都少、易实现,平滑与复杂离散函数上结果可喜;短板也一致——离散函数结果分散度高,只是(μ+λ)-ES 分散中仍保最优。附带的 28_The_world_of_AO_ES.zip(542.28 KB)含前几篇算法代码更新版,作者改过多处以提升搜索力,但明确不对规范准确性打包票。 外汇与贵金属属高风险品种,这类元启发式只负责把参数空间搜得更透,实盘仍可能过拟合。开 MT5 把 zip 里 EA 跑一遍标准测试,比盯排名更有用。