群体优化算法:随机扩散搜索(SDS)·进阶篇
用随机餐厅寻优给参数空间撒点
这套 AO_SDS 类的初始化把坐标维度、种群规模、餐厅数和选餐厅概率一次性灌进去,相当于先把外汇策略寻优的「地图」和「食客数量」定好。MathSrand 用微秒计数重置随机种子,避免每次 MT5 回测开头种群分布雷同。 Init 里 ArrayResize 给 restSpace、cands、range 系列数组按 coords 和 populationSize 开内存,再 for 循环对每个人执行 cands[i].Init(coords) 初始化各维坐标。注意 fB 初始化为 -DBL_MAX,意味着第一次 Revision 任何适应度都会刷新历史最优。 Moving 在 revision==false 时干活:先把每维空间切成 restNumb 段(restSpace[i]=(rangeMax-rangeMin)/restNumb),再对每个人随机落进某段餐厅,用 RNDfromCI 在该段内取连续值,经 SeInDiSp 吸附到合法离散步长。populationSize 若是 50、restNumb 是 10,每人就有 10^coords 种落点可能,种群多样性靠这段撑住。 Revision 只做一件事:扫一遍种群,若某人适应度 cands[i].f 大于 fB 就更新 fB 并拷贝其坐标到 cB。外汇与贵金属杠杆高、滑点跳空频发,这套离散吸附后的参数在实盘前务必用历史数据多跑几轮 Revision 看 fB 收敛曲线。
class="type">void C_AO_SDS::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 restaurantsNumberP, class=class="str">"cmt">//restaurants number const class="type">class="kw">double probabRestP) class=class="str">"cmt">//probability restaurant choosing { MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator fB = -DBL_MAX; revision = false; coords = coordinatesNumberP; populationSize = populationSizeP; restNumb = restaurantsNumberP; probabRest = probabRestP; ArrayResize(restSpace, coords); ArrayResize(cands, populationSize); for (class="type">int i = class="num">0; i < populationSize; i++) cands [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_SDS::Moving() { if (!revision) { for (class="type">int i = class="num">0; i < coords; i++) { restSpace [i] = (rangeMax [i] - rangeMin [i]) / restNumb; } class="type">class="kw">double min = class="num">0.0; class="type">class="kw">double max = class="num">0.0; class="type">int n = class="num">0; class="type">class="kw">double dish = class="num">0.0; for (class="type">int i = class="num">0; i < populationSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { n = (class="type">int)(RNDfromCI(class="num">0, restNumb)); if (n >= restNumb) n = restNumb - class="num">1; cands [i].raddr [c] = n; cands [i].raddrPrev [c] = n; min = rangeMin [c] + restSpace [c] * n; max = min + restSpace [c]; dish = RNDfromCI(min, max); cands [i].c [c] = SeInDiSp(dish, rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_SDS::Revision() { class=class="str">"cmt">//---------------------------------------------------------------------------- for (class="type">int i = class="num">0; i < populationSize; i++) { if (cands [i].f > fB) { fB = cands [i].f; ArrayCopy(cB, cands [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } } class=class="str">"cmt">//----------------------------------------------------------------------------
「交叉与变异里的种群更新逻辑」
这段逻辑干了两件事:先在第一层循环里把每个候选者的当前适应度与上一代做比较,若变好就把 fPrev 和坐标、地址数组整体存档,相当于给进化留底。ArrayCopy 用 WHOLE_ARRAY 把 c 和 raddr 整块搬进 Prev 后缀数组,省去逐元素赋值。 第二层循环才是真正的重组。对每个候选 i 的每一个坐标 c,先随机挑一个种群序号 n,越界就钳到 populationSize-1。若被选中的历史适应度更高,直接继承它的 raddrPrev[c];否则以 probabRest 概率从 restNumb 个离散槽位里重抽,否则保留自己的旧地址。 坐标还原时,用 rangeMin[c] + restSpace[c]*raddr[c] 算出该维度的子区间 [min, max],再塞进 RNDfromCI 取均匀随机数,最后经 SeInDiSp 按步长对齐到合法离散值。外汇与贵金属参数优化中这套机制可能加快逃离局部劣解,但网格搜索空间若设得过宽,MT5 回测耗时倾向指数增长,属典型高风险调参。
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 rnd = class="num">0.0; class="type">int n = class="num">0; for (class="type">int i = class="num">0; i < populationSize; i++) { if (cands [i].f > cands [i].fPrev) { cands [i].fPrev = cands [i].f; ArrayCopy(cands [i].cPrev, cands [i].c, class="num">0, class="num">0, WHOLE_ARRAY); ArrayCopy(cands [i].raddrPrev, cands [i].raddr, class="num">0, class="num">0, WHOLE_ARRAY); } } for (class="type">int i = class="num">0; i < populationSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { n = (class="type">int)(RNDfromCI(class="num">0, populationSize)); if (n >= populationSize) n = populationSize - class="num">1; if (cands [n].fPrev > cands [i].fPrev) { cands [i].raddr [c] = cands [n].raddrPrev [c]; } else { rnd = RNDfromCI(class="num">0.0, class="num">1.0); if (rnd < probabRest) { n = (class="type">int)(RNDfromCI(class="num">0, restNumb)); if (n >= restNumb) n = restNumb - class="num">1; cands [i].raddr [c] = n; } else { cands [i].raddr [c] = cands [i].raddrPrev [c]; } } min = rangeMin [c] + restSpace [c] * cands [i].raddr [c]; max = min + restSpace [c]; cands [i].c [c] = SeInDiSp(RNDfromCI(min, max), rangeMin [c], rangeMax [c], rangeStep [c]); } }
◍ 随机扩散搜索在基准函数上的实测表现
把随机扩散搜索(SDS)丢进 Rastrigin、Forest、Megacity 三类测试函数跑 10000 次迭代,参数设为 C_AO_SDS:100;1000;0.1,打印结果直接拉满预期差。5 维 Rastrigin 得分 0.99921,500 维掉到 0.67300;Forest 在 5 维拿到 0.94910,但 500 维只剩 0.13790;Megacity 离散函数 5 维 0.71667,500 维跌到 0.07960,综合 All score 5.86873。 观察代理移动轨迹会发现非典型行为:代理数量跟适应度函数丘的高度成比例铺开,局部极值处聚类质量很高,几乎不漏掉任何一个山丘。每次迭代收敛近乎无级增加,陷局部极值的概率偏低,即便最尖锐的 Forest 全局极值也没出现逐步收敛卡死。 横向拉表看,SDS 在 19 个算法里最终结果 100.000,比第二名 SSG 的 87.489 高出约 13%。9 个细分测试里 SDS 拿下 4 个第一;唯一翻车的是多变量 Rastrigin,EM 算法以 1.00000 的 1000 维成绩远远甩开 SDS 的 0.63507。Forest 和 Megacity 的 1000 维测试里,唯一压过 SDS 的是 SSG(树苗播种生长)。 值得在 MT5 里复现的是:SDS 纯靠随机游走却能在离散 Megacity 上几乎零故障运行,说明这类轻量搜索器做参数空间粗筛可能比复杂群智能更省算力。外汇与贵金属策略寻参属高风险实验,回测优不代表实盘能复现。
把工具请下神坛
随机扩散搜索(SDS)在含大量局部极值的复杂函数上跑出了全局最优,0–100 评级直方图里它落在高分区,且外部参数最少、计算负载低,这解释了为何作者把它归为易落地的一类优化器。 但作者自己在文末也挑明了:归档里的代码是他按个人经验改过的,不担保与规范算法绝对一致,结论只来自实验。外汇与贵金属 EA 调参用这类算法,仍属高风险——过拟合和样本外崩坏都不会因为换了优化器就消失。 所以真要验证,去 MT5 把 19_The_world_of_AO_SDS.zip 里的脚本拖进策略测试器,拿你自己的品种和周期跑一遍评级表,比信任何「最优」标签都实在。