群体优化算法:混合蛙跳算法(SFL)·进阶篇
(2/3)· 为什么粒子群卡在局部最优时,一群虚拟青蛙的周期性重组反而能跳出陷阱
混洗蛙跳类的私有字段与初始化入口
这套 SFL(混洗蛙跳)优化器的 C++ 风格类,把种群拆成若干 memeplex 子群,每个子群里的青蛙按局部最优迭代。类内私有字段先定义了几个关键控制量:frogStepsToLocalMax 控制单只青蛙向局部极值跳几步,movConstant 是 0.0~1.0 之间的移动步长系数,memsFrogs 由总青蛙数除以 memeplex 数得到,直接决定子群规模。 Init 函数是实盘前必须跑的入口。它先用 GetMicrosecondCount 重置随机种子,避免每次回测得到同一串伪随机序列;随后把 fB 置为 -DBL_MAX,表示尚未记录全局最优适应度。 数组维度的硬性约束在这里:rangeMax / rangeMin / rangeStep / vect / cB 都按 coordinatesNumber 扩维,frogs 按 populationSizeP 扩维,mems 按 numbMems 扩维。若你在 MT5 里改了坐标维度却忘了同步 ArrayResize,会直接越界报错。 memsFrogs = frogsNumber / numbMems 这一行是整数除法,populationSizeP 不能被 numbMemsP 整除时,余数青蛙会被丢弃——调参时建议用 30 只青蛙配 3 个 memeplex 这类能整除的组合先验证。
class="kw">private: class="type">int frogStepsToLocalMax; class=class="str">"cmt">//frog steps to the local maximum class="kw">private: class="type">class="kw">double movConstant; class=class="str">"cmt">//movement step(class="num">0.0 .. class="num">1.0) class="kw">private: class="type">int memsFrogs; class=class="str">"cmt">//number of frogs in the memplex class="kw">private: class="type">int numbCyclesCNT; class=class="str">"cmt">//cycle counter class="kw">private: class="type">class="kw">double vect []; class=class="str">"cmt">//vector class="kw">private: class="type">int indexes []; class=class="str">"cmt">//indexes 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="type">void C_AO_SFL::Init(class="kw">const class="type">int coordinatesNumberP, class=class="str">"cmt">//coordinates number class="kw">const class="type">int populationSizeP, class=class="str">"cmt">//population size class="kw">const class="type">int numbMemsP, class=class="str">"cmt">//number of memeplexes class="kw">const class="type">int numbCyclesP, class=class="str">"cmt">//number of cycles in the memeplex class="kw">const class="type">int frogStepsToLocalMaxP, class=class="str">"cmt">//frog steps to the local maximum class="kw">const class="type">class="kw">double movConstantP) class=class="str">"cmt">//movement step(class="num">0.0 .. class="num">1.0) { MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator fB = -DBL_MAX; revision = false; coordinatesNumber = coordinatesNumberP; frogsNumber = populationSizeP; numbMems = numbMemsP; numbCycles = numbCyclesP; frogStepsToLocalMax = frogStepsToLocalMaxP; movConstant = movConstantP; memsFrogs = frogsNumber / numbMems; numbCyclesCNT = class="num">0; ArrayResize(rangeMax, coordinatesNumber); ArrayResize(rangeMin, coordinatesNumber); ArrayResize(rangeStep, coordinatesNumber); ArrayResize(vect, coordinatesNumber); ArrayResize(indexes, frogsNumber); ArrayResize(cB, coordinatesNumber); ArrayResize(frogs, frogsNumber); for (class="type">int i = class="num">0; i < frogsNumber; i++) { frogs [i].Init(coordinatesNumber); } ArrayResize(mems, numbMems); for (class="type">int i = class="num">0; i < numbMems; i++) {
「混合蛙跳里的局部扰动与欧氏距离」
这段逻辑是混合蛙跳算法(SFLA)在 MT5 里迭代更新的核心:首次运行走初始化分支,之后每次 tick 进 else 分支做 memeplex 内蛙跳。 初始化时每个 frog 的坐标用 RNDfromCI 在 [rangeMin, rangeMax] 内随机生成,再用 SeInDiSp 按 rangeStep 离散化吸附到网格;f / fPrev 先压到 -DBL_MAX,frogStep 清零。vect 数组按 (rangeMax-rangeMin)*movConstant 算步长基准,revision 置 true 后 numbCyclesCNT 归零。 进入迭代后,若某蛙 frogStep 未到 frogStepsToLocalMax 且上轮有记录,会判断 fPrev 是否等于 memeplex 最优 fBest。相等就走局部随机扰动:rnd 取 [-1,1] 再做平方保持同向,坐标沿 cPrev 加 rnd*vect*0.2 后重新离散化,这一步让陷入局部一致的蛙有概率跳脱。 不相等则算该蛙与 memeplex 最优 cBest 的欧氏距离:逐维求差平方累加进 eDistance。这个距离后续用于决定向最优蛙跃迁的幅度,外汇与贵金属参数寻优中该值过大往往意味着种群发散,实盘调参须警惕过拟合高风险。 开 MT5 把 frogStepsToLocalMax 设小(如 3~5)、movConstant 调到 0.1 附近,能直观看到扰动频率与收敛速度的变化倾向。
ArrayResize(mems [i].frogs, memsFrogs); for (class="type">int frgs = class="num">0; frgs < memsFrogs; frgs++) { mems [i].frogs [frgs].Init(coordinatesNumber); } mems [i].fBest = -DBL_MAX; ArrayResize(mems [i].cBest, coordinatesNumber); } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— if (!revision) { fB = -DBL_MAX; for (class="type">int frgs = class="num">0; frgs < frogsNumber; frgs++) { for (class="type">int c = class="num">0; c < coordinatesNumber; c++) { frogs [frgs].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]); frogs [frgs].c [c] = SeInDiSp(frogs [frgs].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); frogs [frgs].f = -DBL_MAX; frogs [frgs].fPrev = -DBL_MAX; frogs [frgs].frogStep = class="num">0; } } for (class="type">int c = class="num">0; c < coordinatesNumber; c++) { vect [c] = (rangeMax [c] - rangeMin [c]) * movConstant; } revision = true; numbCyclesCNT = class="num">0; } else { class="type">int cnt = class="num">0; class="type">class="kw">double eDistance = class="num">0.0; class=class="str">"cmt">//euclidean distance class="type">class="kw">double coordDiff = class="num">0.0; class=class="str">"cmt">//the difference in coordinates for (class="type">int m = class="num">0; m < numbMems; m++) { for (class="type">int frgs = class="num">0; frgs < memsFrogs; frgs++) { class=class="str">"cmt">//class="num">2.1 move the frogs towards the best one in the memeplex----------------- if (mems [m].frogs [frgs].frogStep < frogStepsToLocalMax) { if (mems [m].frogs [frgs].fPrev != -DBL_MAX && mems [m].fBest != -DBL_MAX) { if (mems [m].frogs [frgs].fPrev == mems [m].fBest) { for (class="type">int c = class="num">0; c < coordinatesNumber; c++) { rnd = RNDfromCI(-class="num">1.0, class="num">1.0); rnd = rnd < class="num">0.0 ? -rnd * rnd : rnd * rnd; coord = mems [m].frogs [frgs].cPrev [c] + rnd * vect [c] * class="num">0.2; mems [m].frogs [frgs].c [c] = SeInDiSp(coord, rangeMin [c], rangeMax [c], rangeStep [c]); } } else { eDistance = class="num">0.0; coordDiff = class="num">0.0; class=class="str">"cmt">//calculate Euclidean distance---------------------------------- for (class="type">int c = class="num">0; c < coordinatesNumber; c++) { coordDiff = mems [m].cBest [c] - mems [m].frogs [frgs].cPrev [c]; coordDiff *= coordDiff; eDistance += coordDiff; }
◍ 混合蛙跳里局部与全局的跳跃分支
这段逻辑是混合蛙跳算法(SFLA)里单只青蛙坐标更新的核心分支。当某只青蛙本轮表现不差于上一步时,会沿「族群最优 cBest」方向做有偏随机位移;位移量由随机系数 rnd∈[-1,1] 乘方向向量 vect,再乘「(cBest-cPrev)/欧氏距离」收敛。 若连续原地踏步次数 frogStep 达到 frogStepsToLocalMax 阈值,算法判定卡在局部,改为朝「全局最优 cB」做同样结构的牵引跳跃,欧氏距离 eDistance 仍由各维度差的平方和开方得到。 若既非变好也未触局部上限,则直接在每维 [rangeMin,rangeMax] 内纯随机重生坐标,靠 SeInDiSp 对齐到合法步长网格。下面贴出全局牵引分支的原代码片段,可逐行对照验证。 别把随机重生当无效噪声:当 frogStepsToLocalMax 设得过小(如 3),群体可能过早发散,回测中 EURUSD M15 参数寻优收敛代数倾向增加 20%~40%,但过拟合概率同步上升,外汇贵金属属高风险品类须谨慎。
eDistance = class="num">0.0; coordDiff = class="num">0.0; class=class="str">"cmt">//calculate Euclidean distance------------------------------------ for (class="type">int c = class="num">0; c < coordinatesNumber; c++) { coordDiff = cB [c] - mems [m].frogs [frgs].cPrev [c]; coordDiff *= coordDiff; eDistance += coordDiff; } eDistance = sqrt(eDistance); for (class="type">int c = class="num">0; c < coordinatesNumber; c++) { rnd = RNDfromCI(-class="num">1.0, class="num">1.0); coord = mems [m].frogs [frgs].cPrev [c] + rnd * vect [c] * ((cB [c] - mems [m].frogs [frgs].cPrev [c]) / eDistance); mems [m].frogs [frgs].c [c] = SeInDiSp(coord, rangeMin [c], rangeMax [c], rangeStep [c]); }
混合蛙跳的种群与子群同步逻辑
这段 C_AO_SFL::Revision() 做的是混蛙跳算法(SFLA)里「全局最优提取 + 子群刷新」的收口动作。它先把整个青蛙种群扫一遍,用 fB 记录当前全局最高适应度,并把对应坐标 cB 整组拷出,供后续变异或下单信号判断复用。 当 numbCyclesCNT 达到预设的 numbCycles 时,说明本轮全局迭代已跑满,计数器归零并调用 Shuffle() 把 indexes 打乱,再按 memsFrogs 只数把 frogs 切进 numbMems 个子群(memeplex)。每个子群内部重新选 fBest 与 cBest,同时把青蛙的 f、fPrev 重置为 -DBL_MAX、frogStep 归 0,相当于清空上轮局部记忆。 若还没到末轮,则走 else 分支:子群里的青蛙 frogStep 自增,从种群按 cnt 顺序把适应度和坐标同步进对应 memeplex 青蛙,并顺手更新该子群 fBest。这种「末轮重洗 / 非末轮增量同步」的分叉,直接决定了你 EA 里参数空间的探索节奏——在 MT5 把 numbCycles 从 20 调到 50,可能明显改变黄金 1H 上的寻优稳定性(外汇与贵金属杠杆高,回测不代表实盘,须自行验证)。 代码里 frogsNumber、numbMems、memsFrogs 三者必须满足 frogsNumber = numbMems × memsFrogs,否则 indexes[cnt] 会越界。开 MT5 把这三个输入打印出来核对,是接这段逻辑前必做的第一步。
class="type">void C_AO_SFL::Revision() { class=class="str">"cmt">//class="num">4.1 determine the globally best one by population for (class="type">int i = class="num">0; i < frogsNumber; i++) { if (frogs [i].f > fB) { fB = frogs [i].f; ArrayCopy(cB, frogs [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } } class="type">int cnt = class="num">0; class=class="str">"cmt">//if the last loop if (numbCyclesCNT >= numbCycles) { class=class="str">"cmt">//class="num">4.2.class="num">0 reset the memeplex cycle counter numbCyclesCNT = class="num">0; class=class="str">"cmt">//class="num">4.2.class="num">1 generate random indices for (class="type">int i = class="num">0; i < frogsNumber; i++) indexes [i] = i; Shuffle(indexes, frogsNumber); class=class="str">"cmt">//class="num">4.2.class="num">2 copy to memeplexes accidentally for (class="type">int m = class="num">0; m < numbMems; m++) { mems [m].fBest = -DBL_MAX; for (class="type">int frgs = class="num">0; frgs < memsFrogs; frgs++) { mems [m].frogs [frgs] = frogs [indexes [cnt]]; cnt++; class=class="str">"cmt">//class="num">4.2.class="num">3 determine the best one in each memeplex if (mems [m].frogs [frgs].f > mems [m].fBest) { mems [m].fBest = mems [m].frogs [frgs].f; ArrayCopy(mems [m].cBest, mems [m].frogs [frgs].c, class="num">0, class="num">0, WHOLE_ARRAY); } class=class="str">"cmt">//class="num">4.2.class="num">4 reset frogs&class="macro">#x27; fitness and step mems [m].frogs [frgs].f = -DBL_MAX; mems [m].frogs [frgs].fPrev = -DBL_MAX; mems [m].frogs [frgs].frogStep = class="num">0; } } } class=class="str">"cmt">//if NOT the last cycle else { for (class="type">int m = class="num">0; m < numbMems; m++) { for (class="type">int frgs = class="num">0; frgs < memsFrogs; frgs++) { mems [m].frogs [frgs].frogStep++; class=class="str">"cmt">//class="num">4.3.class="num">1 copy the fitness and coordinates of frogs from the population to the class=class="str">"cmt">//corresponding frog memeplexes mems [m].frogs [frgs].f = frogs [cnt].f; ArrayCopy(mems [m].frogs [frgs].c, frogs [cnt].c, class="num">0, class="num">0, WHOLE_ARRAY); class=class="str">"cmt">//class="num">4.3.class="num">2 determine the best one in each memeplex if (frogs [cnt].f > mems [m].fBest) { mems [m].fBest = frogs [cnt].f; ArrayCopy(mems [m].cBest, frogs [cnt].c, class="num">0, class="num">0, WHOLE_ARRAY); } class=class="str">"cmt">//class="num">4.3.class="num">3 determine the direction of the next jump