种群优化算法:鱼群搜索(FSS)·进阶篇
「鱼群重组与最优解刷新逻辑」
这段 C_AO_FSS 类的 Regrouping 方法实现了人工鱼群算法里的「游动阶段切换」:当 swimmingRegime 为 1 时,首次进入会做初始化拷贝,把 new_position 覆盖到 current_position,并将 delta 清零、init 置 true 后直接返回;非首次则逐条比对 new_fitness 与历史 fitness,更优才记录 delta_position 与 delta_fitness,否则归零。 阶段 1 处理完会把 swimmingRegime 切到 2,并依次调用 updates_optimal_solution 与 apply_feeding;若当前不在阶段 1,则复位成阶段 1 并刷新全局最优。注意这里用 fabs 注释提示过差值可能取绝对值,但代码实际未取,直接用了新减旧,回测时若遇到 fitness 回撤会被忽略。 updates_optimal_solution 是纯遍历:任一条鱼 fitness 超过 global_best 就拷贝其坐标到 global_best_position。外汇与贵金属波动下用这类群体寻优,参数维度爆炸时易过拟合,属高风险玩法,建议先在 MT5 策略测试器用小样本验证。
class="type">void C_AO_FSS::Regrouping() { if (swimmingRegime == class="num">1) { if (!init) { for (class="type">int f = class="num">0; f < num_of_individuos; f++) { ArrayCopy(fishes [f].current_position, fishes [f].new_position, class="num">0, class="num">0, WHOLE_ARRAY); ArrayInitialize(fishes [f].delta_position, class="num">0.0); fishes [f].fitness = fishes [f].new_fitness; fishes [f].delta_fitness = class="num">0.0; } init = true; class="kw">return; } for (class="type">int f = class="num">0; f < num_of_individuos; f++) { if (fishes [f].new_fitness > fishes [f].fitness) { fishes [f].delta_fitness = fishes [f].new_fitness - fishes [f].fitness; fishes [f].fitness = fishes [f].new_fitness; for (class="type">int d = class="num">0; d < dimensions; d++) { fishes [f].delta_position [d] = fishes [f].new_position [d] - fishes [f].current_position [d]; } ArrayCopy(fishes [f].current_position, fishes [f].new_position, class="num">0, class="num">0, WHOLE_ARRAY); } else { ArrayInitialize(fishes [f].delta_position, class="num">0.0); fishes [f].delta_fitness = class="num">0.0; } } swimmingRegime = class="num">2; updates_optimal_solution(); apply_feeding(); class="kw">return; } swimmingRegime = class="num">1; updates_optimal_solution(); } class="type">void C_AO_FSS::updates_optimal_solution() { for (class="type">int f = class="num">0; f < num_of_individuos; f++) { if (fishes [f].fitness > global_best) { global_best = fishes [f].fitness; ArrayCopy(global_best_position, fishes [f].current_position, class="num">0, class="num">0, WHOLE_ARRAY); } } }
◍ 鱼群算法的进食与两类集体移动
这段 C_AO_FSS 类的实现把鱼群搜索拆成了三个动作:进食(feeding)、本能集体移动、意志集体移动。外汇与贵金属参数优化里跑这类群体算法,务必认清高杠杆下的回测过拟合风险,实盘可能偏离历史表现。 apply_feeding 先扫一遍所有鱼,找出 delta_fitness 的最大值 max_delta_fitness(初始为 -DBL_MAX)。若最大值不为 0,每条鱼的 weight 加上自身 delta_fitness 与最大值的比值;若全为 0 则权重统一置 1。这一步让 fitness 增量大的个体在后续获得更高话语权。 apply_instintive_collective_movement 用 sum_delta_fitness 做归一化:每条鱼在每一维的位移方向乘自身 delta_fitness,再除以总和,得到 collective_instinct 向量,然后加到 current_position 上并用 SeInDiSp 夹回参数边界。注意若 sum_delta_fitness 为 0 会跳过除法直接沿用位移乘积,逻辑上等价于不归一。 apply_collective_volitive_movement 前半段算加权质心 barycenter:current_total_weight 为全部 weight 之和,各维质心 = Σ(weight*current_position)/total_weight。search_operator 由 current_total_weight 是否大于 total_weight 决定取 1 或 -1,用来控制鱼群向质心聚拢还是发散。开 MT5 把 num_of_individuos 调到 50 以上,能明显看到质心计算耗时随维度线性增长。
{
r = RNDfromCI(-class="num">1.0, class="num">1.0);
fishes [f].new_position [d] = fishes [f].current_position [d] + step_ind [d] * r;
fishes [f].new_position [d] = SeInDiSp(fishes [f].new_position [d], rangeMin [d], rangeMax [d], rangeStep [d]);
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_FSS::apply_feeding()
{
class="type">class="kw">double max_delta_fitness = -DBL_MAX;
class=class="str">"cmt">//find the maximum weight among fish
for (class="type">int f = class="num">0; f < num_of_individuos; f++)
{
if (fishes [f].delta_fitness > max_delta_fitness) max_delta_fitness = fishes [f].delta_fitness;
}
class=class="str">"cmt">//feed the fish
for (class="type">int f = class="num">0; f < num_of_individuos; f++)
{
if (max_delta_fitness != class="num">0.0)
{
fishes [f].weight = fishes [f].weight + (fishes [f].delta_fitness / max_delta_fitness);
}
else fishes [f].weight = class="num">1;
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_FSS::apply_instintive_collective_movement()
{
class="type">class="kw">double sum_delta_fitness = class="num">0.0;
for (class="type">int f = class="num">0; f < num_of_individuos; f++)
{
sum_delta_fitness += fishes [f].delta_fitness;
}
for (class="type">int f = class="num">0; f < num_of_individuos; f++)
{
for (class="type">int d = class="num">0; d < dimensions; d++)
{
collective_instinct [d] = fishes [f].delta_position [d] * fishes [f].delta_fitness;
if (sum_delta_fitness != class="num">0.0)
{
collective_instinct [d] /= sum_delta_fitness;
}
fishes [f].new_position [d] = fishes [f].current_position [d] + collective_instinct [d];
fishes [f].new_position [d] = SeInDiSp(fishes [f].new_position [d], rangeMin [d], rangeMax [d], rangeStep [d]);
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_FSS::apply_collective_volitive_movement()
{
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double current_total_weight = class="num">0.0;
for (class="type">int f = class="num">0; f < num_of_individuos; f++)
{
current_total_weight += fishes [f].weight;
}
ArrayInitialize(barycenter, class="num">0.0);
for (class="type">int f = class="num">0; f < num_of_individuos; f++)
{
for (class="type">int d = class="num">0; d < dimensions; d++)
{
barycenter [d] += fishes [f].weight * fishes [f].current_position [d];
}
}
for (class="type">int d = class="num">0; d < dimensions; d++)
{
barycenter [d] /= current_total_weight;
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double search_operator = current_total_weight > total_weight ? class="num">1.0 : -class="num">1.0;
class="type">class="kw">double r = class="num">0.0;
class="type">class="kw">double pos = class="num">0.0;
for (class="type">int f = class="num">0; f < num_of_individuos; f++)
{
for (class="type">int d = class="num">0; d < dimensions; d++)
{鱼群位置更新与总权重统计
人工鱼群算法里,单条鱼在第 d 维的下一步位置由当前位置、群体质心与随机扰动共同决定。代码先用 RNDfromCI(0.0,1.0) 取均匀随机数 r,再按 (pos - barycenter[d]) * step_vol[d] * r 乘以 search_operator 做偏移,使搜索既有向心的聚集倾向,也保留随机扩散可能。 偏移后必须过 SeInDiSp 把新坐标夹回 [rangeMin[d], rangeMax[d]] 并对齐 rangeStep[d] 离散网格,否则越界个体会让后续适应度计算直接崩。外汇与贵金属参数寻优中这一步常决定收敛是否稳定,属高风险调参环节。 update_total_weight 则是 O(n) 的累加:遍历 num_of_individuos 条鱼把各自 weight 求和赋给 total_weight。该值后续用于轮盘赌选择或群体优劣评估,若漏调会在迭代中让选择概率失真。 开 MT5 把这段塞进 EA 的 OnInit 后单步,看 total_weight 是否随 fishes[].weight 变更实时刷新,就能验证你那版 FSS 框架有没有接错回调。
r = RNDfromCI(class="num">0.0, class="num">1.0); pos = fishes [f].current_position [d]; fishes [f].new_position [d] = pos + (((pos - barycenter [d]) * step_vol [d] * r) * search_operator); fishes [f].new_position [d] = SeInDiSp(fishes [f].new_position [d], rangeMin [d], rangeMax [d], rangeStep [d]); } } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_FSS::update_total_weight() { total_weight = class="num">0.0; for (class="type">int f = class="num">0; f < num_of_individuos; f++) { total_weight += fishes [f].weight; } } class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
「鱼群搜索跑完基准测试的真实表现」
鱼群搜索(FSS)在 EURUSD M1 上跑了三组测试函数,每组各测 2 / 40 / 1000 参数,函数迭代次数固定 10000。日志里能直接看到:Skin 单函数时得分 0.99391,到了 500 函数并行时掉到 0.11341;Forest 从 0.85172 滑到 0.03223;Megacity 从 0.91667 跌到 0.02583。综合所有项,C_AO_FSS 总评分 0.41225,仅略高于 PSO 的 0.40837,明显弱于 COAm 的 0.51256。 平滑的 Skin 函数是 FSS 最舒服的场景。算法靠每个坐标的增量和适应度变化去“贴”曲面,所以单函数时几乎追平最优;但参数规模一大,分散能力反而拖累稳定性。Forest 函数它只能拿到低于平均的结果,说明空间导航依赖平滑梯度,一旦全局最大值藏得深就找不动。 Megacity 是离散型函数,FSS 的弱点被放大:当代理者附近没有增量,它缺乏长距离跳跃机制,容易卡死在局部极值。500 函数时得分仅 0.02583,和随机搜索拉不开差距。表格里其它算法在离散项也不算强,但 FSS 的可扩展性弱这一点被坐实了。 开 MT5 把 7_The_world_of_AO_yFSSa.zip 里的 EA 拖到 EURUSD M1,自己改 Func runs 和函数类型,能复现上面那串日志。外汇与贵金属测试环境波动大,这类优化结果只反映算法特性,实盘应用属高风险。 别把总分当能力全景 FSS 总分 0.412 看着垫底附近,但 Skin 单函数 0.99391 说明它对平滑连续问题仍有一战之力。选算法要看你的目标函数形态,而不是盯着总表排名。
◍ 把这条线请下神坛
强化学习在行情建模里常被神化成「自动找圣杯」的管线,但回到 MQL5 这套实现,核心只是把代理在状态 s 下执行动作 a、延迟 k 步才收到奖励 r 的链路显式建模出来。 人为设定即时奖励的最大问题,是动作与反馈之间的时滞被强行抹平——实盘里贵金属突破后往往隔 3~5 根小时线才走完一段趋势,即时打分反而教坏策略。 时间差分类延迟回报算法把 k 步后的 reward 按折扣累回原动作,外汇与贵金属高杠杆下仍可能连续止损,这只是把拟合误差从「即时」挪到「滞后」,别当成免死金牌。 下次写 EA 前先问一句:我的奖励函数,敢不敢承认自己至少慢了两根 K 线。