种群优化算法:蝙蝠算法(BA)·进阶篇
◍ 蝙蝠算法初始化与首飞逻辑
这段 C_AO_BA 类的构造函数把蝙蝠群优化的超参数一次性灌入对象:脉冲率上下限、响度范围、alpha 与 gamma 收敛系数,以及最大迭代次数 maxIterP 都被固化为成员。数组 rangeMax / rangeMin / rangeStep 按维度 params 扩容,bats 按 batsNumber 扩容,每只蝙蝠的 position、speed 均为 params 维,初值 fitness 设为 -DBL_MAX。 Flight() 是核心步进函数。首次调用时 firstFlight 为 false,会遍历每只蝙蝠、每个维度,用 RNDfromCI 在参数区间内随机撒点,再用 SeInDiSp 做离散空间对齐;frequency 在 MIN_FREQ~MAX_FREQ 间随机,initPulseRate 取 MIN_PULSE~MAX_PULSE/2,loudness 取 MAX_LOUDNESS/2~MAX_LOUDNESS。首飞结束后 firstFlight 置 true,后续 epoch 进入 else 分支做平均响度计算与位置更新。 开 MT5 把这段贴进 EA 的 include 类,把 batsNumber 设成 30、params 设成你的指标维度,能在策略测试器里直接观察首代种群分布是否覆盖全区间。外汇与贵金属杠杆高,回测优解实盘可能失效,参数需谨慎验证。
const class="type">class="kw">double min_PULSE_P, const class="type">class="kw">double max_PULSE_P, const class="type">class="kw">double alpha_P, const class="type">class="kw">double gamma_P, const class="type">int maxIterP) { MathSrand(GetTickCount()); fB = -DBL_MAX; params = paramsP; batsNumber = batsNumberP; MIN_FREQ = min_FREQ_P; MAX_FREQ = max_FREQ_P; MIN_LOUDNESS = min_LOUDNESS_P; MAX_LOUDNESS = max_LOUDNESS_P; MIN_PULSE = min_PULSE_P; MAX_PULSE = max_PULSE_P; ALPHA = alpha_P; GAMMA = gamma_P; maxIter = maxIterP; ArrayResize(rangeMax, params); ArrayResize(rangeMin, params); ArrayResize(rangeStep, params); firstFlight = false; ArrayResize(bats, batsNumber); for (class="type">int i = class="num">0; i < batsNumber; i++) { ArrayResize(bats [i].position, params); ArrayResize(bats [i].auxPosition, params); ArrayResize(bats [i].speed, params); bats [i].fitness = -DBL_MAX; } ArrayResize(cB, params); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_BA::Flight(class="type">int epoch) { currentIteration = epoch; class=class="str">"cmt">//============================================================================ if (!firstFlight) { fB = -DBL_MAX; class=class="str">"cmt">//-------------------------------------------------------------------------- for (class="type">int b = class="num">0; b < batsNumber; b++) { for (class="type">int p = class="num">0; p < params; p++) { bats [b].position [p] = RNDfromCI(rangeMin [p], rangeMax [p]); bats [b].position [p] = SeInDiSp(bats [b].position [p], rangeMin [p], rangeMax [p], rangeStep [p]); bats [b].auxPosition [p] = bats [b].position [p]; bats [b].speed [p] = class="num">0.0; bats [b].frequency = RNDfromCI(MIN_FREQ, MAX_FREQ); bats [b].initPulseRate = RNDfromCI(MIN_PULSE, MAX_PULSE / class="num">2); bats [b].pulseRate = bats [b].initPulseRate; bats [b].loudness = RNDfromCI(MAX_LOUDNESS / class="num">2, MAX_LOUDNESS); bats [b].fitness = -DBL_MAX; bats [b].fitnessBest = -DBL_MAX; } } firstFlight = true; } class=class="str">"cmt">//============================================================================ else { class="type">class="kw">double avgLoudness = class="num">0; for (class="type">int b = class="num">0; b < batsNumber; b++)
「蝙蝠算法里的位置更新与接收判据」
这段实现把蝙蝠群优化(BA)的核心迭代拆成了几个内联方法,直接挂在 C_AO_BA 类下,跑在 MT5 的策略优化或自定义指标计算线程里。 先说 Walk:每只蝙蝠按频率调速度,频率在 MIN_FREQ 到 MAX_FREQ 之间均匀随机;速度增量正比于「自身位置减当前全局最优 cB」,再叠到位置上生成 auxPosition,最后用 SeInDiSp 把越界值拉回 [rangeMin, rangeMax] 并按 rangeStep 离散化。 AproxBest 只在脉冲随机值大于该蝙蝠 pulseRate 时触发,用平均响度 averageLoudness 乘 [-1,1] 随机量去扰动全局最优附近,同样走离散化约束——这是局部开采的逻辑。 Preparation 做两件事:先扫一遍把 fitness 最高的蝙蝠写进 cB;再对「随机响度低于自身 loudness 且 fitness 不低于历史最优」的蝙蝠调用 AcceptNewSolutions。 AcceptNewSolutions 里 loudness 乘 ALPHA 衰减,pulseRate 走 initPulseRate*(1-exp(-GAMMA*iter)),iter 由当前代数经 Scale 映射到 0~10。外汇与贵金属参数寻优用这套高风险,回测过拟合概率偏高,建议开 MT5 把 ALPHA、GAMMA 设为输入变量做敏感性验证。
{
avgLoudness += bats [b].loudness;
}
avgLoudness /= batsNumber;
for (class="type">int b = class="num">0; b < batsNumber; b++)
{
Walk(bats [b]);
if (RNDfromCI(MIN_PULSE, MAX_PULSE) > bats [b].pulseRate)
{
AproxBest(bats [b], avgLoudness);
}
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::Preparation()
{
class=class="str">"cmt">//----------------------------------------------------------------------------
for (class="type">int b = class="num">0; b < batsNumber; b++)
{
if (bats [b].fitness > fB)
{
fB = bats [b].fitness;
ArrayCopy(cB, bats [b].auxPosition, class="num">0, class="num">0, WHOLE_ARRAY);
}
}
class=class="str">"cmt">//----------------------------------------------------------------------------
for (class="type">int b = class="num">0; b < batsNumber; b++)
{
if (RNDfromCI(MIN_LOUDNESS, MAX_LOUDNESS) < bats [b].loudness && bats [b].fitness >= bats [b].fitnessBest)
{
AcceptNewSolutions(bats [b]);
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::Walk(S_Bat &bat)
{
for (class="type">int j = class="num">0; j < params; ++j)
{
bat.frequency = MIN_FREQ + (MAX_FREQ - MIN_FREQ) * RNDfromCI(class="num">0.0, class="num">1.0);
bat.speed [j] = bat.speed [j] + (bat.position [j] - cB [j]) * bat.frequency;
bat.auxPosition [j] = bat.position [j] + bat.speed [j];
bat.auxPosition [j] = SeInDiSp(bat.auxPosition [j], rangeMin [j], rangeMax [j], rangeStep [j]);
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::AproxBest(S_Bat &bat, class="type">class="kw">double averageLoudness)
{
for (class="type">int j = class="num">0; j < params; ++j)
{
bat.auxPosition [j] = cB [j] + averageLoudness * RNDfromCI(-class="num">1.0, class="num">1.0);
bat.auxPosition [j] = SeInDiSp(bat.auxPosition [j], rangeMin [j], rangeMax [j], rangeStep [j]);
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BA::AcceptNewSolutions(S_Bat &bat)
{
ArrayCopy(bat.position, bat.auxPosition, class="num">0, class="num">0, WHOLE_ARRAY);
bat.fitnessBest = bat.fitness;
bat.loudness = ALPHA * bat.loudness;
class="type">class="kw">double iter = Scale(currentIteration, class="num">1, maxIter, class="num">0.0, class="num">10.0, false);
bat.pulseRate = bat.initPulseRate *(class="num">1.0 - exp(-GAMMA * iter));
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————优化算法跑分台:BA 在平滑面吃香,离散面露怯
这一轮把测试台改了三处:变量维度拉到 10 / 50 / 1000;用 Rastrigin 替掉原来的 Skin 函数(平滑多极值,中心全局最小、四角全局最大);评分改成相对归一——同表最佳 1.0、最差 0.0,再放大成 1~100 的互比分。这样能直接看算法在不同复杂度函数上的真实落差。 蝙蝠算法(BA)在 Rastrigin 上表现抢眼:50 维和 1000 维都拿了该函数的头名,且变量越多结果越稳,说明它对平滑面的可扩展性不错。但在 Forest、Megacity 这类离散/裂隙函数上,BA 容易把坐标群困在局部高原,缺乏类似 COA 的 Levy 飞行跃迁机制,陷入局部极值的概率偏高。 最终评级里 ACOm 以 100 分居首(九测五优),COAm 78.177、FAm 74.486 紧随,BA 自身 62.962。BA 的硬伤是参数太多且各参数对收敛性质影响极大,新手调参后结果可能天差地别,经验不足者不建议直接上。 下面这段是 BA 测试脚本在 EURUSD M1 上实跑 Print 出来的片段,能直接抄进 MT5 脚本验证: C_AO_BA:50;0.0;1.0;0.0;1.5;0.0;1.0;0.3;0.3 是初始化参数行;随后 5/25/500 维 Rastrigin 各跑 10000 次,Score 分别为 0.82562 / 0.81175 / 0.74151,随维度上升而下滑;5 维 Forest 的 Score 仅 0.33156,印证离散面弱势。外汇与贵金属优化属高风险,回测分高不代表实盘能复现。
class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">46.384 Test_AO_BA(EURUSD,M1) C_AO_BA:class="num">50;class="num">0.0;class="num">1.0;class="num">0.0;class="num">1.5;class="num">0.0;class="num">1.0;class="num">0.3;class="num">0.3 class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">46.384 Test_AO_BA(EURUSD,M1) ============================= class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">48.451 Test_AO_BA(EURUSD,M1) class="num">5 Rastrigin&class="macro">#x27;s; Func runs class="num">10000 result: class="num">66.63334336098077 class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">48.451 Test_AO_BA(EURUSD,M1) Score: class="num">0.82562 class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">52.630 Test_AO_BA(EURUSD,M1) class="num">25 Rastrigin&class="macro">#x27;s; Func runs class="num">10000 result: class="num">65.51391114042588 class="num">2022.12.class="num">28 class="num">17:class="num">13:class="num">52.630 Test_AO_BA(EURUSD,M1) Score: class="num">0.81175 class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">27.234 Test_AO_BA(EURUSD,M1) class="num">500 Rastrigin&class="macro">#x27;s; Func runs class="num">10000 result: class="num">59.84512760590815 class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">27.234 Test_AO_BA(EURUSD,M1) Score: class="num">0.74151 class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">27.234 Test_AO_BA(EURUSD,M1) ============================= class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">32.280 Test_AO_BA(EURUSD,M1) class="num">5 Forest&class="macro">#x27;s; Func runs class="num">10000 result: class="num">0.5861602092218606 class="num">2022.12.class="num">28 class="num">17:class="num">14:class="num">32.280 Test_AO_BA(EURUSD,M1) Score: class="num">0.33156
◍ 森林与都市模型在 EURUSD M1 上的跑分对照
同一套测试脚本在 EURUSD M1 周期下,分别用 Forest 和 Megacity 两种结构跑了 10000 次函数调用。Forest 在 25 棵树时 Func runs 结果 0.2896、Score 0.16379;加到 500 棵树反而掉到 0.0987、Score 0.05582,过拟合倾向明显。 Megacity 这边 5 个单位时 Func runs 高达 3.32、Score 0.27667,随单位数上升到 500 回落到 0.4076、Score 0.03397。小体量结构在 M1 噪声里更灵敏,堆量之后边际收益递减。
- 01.03 的 C_AO 三组对照里,RND 汇总值 0.6893,PSO 汇总值 1.0530,ACOm 前两组全 1.0。ACOm 在样本上几乎吃满分数,但实盘外汇/贵金属高风险,这种饱和分数往往代表回测环境被特定形态主导,直接跟单可能亏在滑点和点差。
开 MT5 把 Test_AO_BA 的 Forest/Megacity 数量参数从 5 拉到 500 逐档跑一遍,重点看 Score 随规模的非单调变化,比盯绝对数值更有用。