种群优化算法:鱼群搜索(FSS)(基础篇)
「鱼群搜索在 MT5 里的种群优化思路」
鱼群搜索(FSS)属于种群优化算法的一种,在 MT5 中常被用来替代网格遍历,去搜参数空间里更优的 EA 配置。它模拟鱼群觅食时的个体移动、聚群和追尾行为,靠群体随机试探逐步逼近低回撤、高夏普的解。 原文示例发布于 2023 年 4 月 27 日,在 MetaTrader 5 板块下获得 1352 次查看、4 条讨论,作者 Andrey Dik 将其拆为概述、算法描述、测试结果三块来写。 对外汇与贵金属 EA 做种群优化,本质上是在高杠杆、高波动环境里找稳健参数;这类品种风险极高,回测漂亮不代表实盘能复现,开 MT5 用策略测试器跑一遍自己的品种才能确认。
◍ 鱼群聚集与群体智慧的数学雏形
鱼群并非随机堆砌,而是分结构化与非结构化两类。非结构化聚集多为混种、混尺寸,因局部食物或巢址临时凑堆;一旦同周期、同物种个体开始社会式互动,便升级为具备组织活性的团体,在抗捕食与争食间取得折衷。 自然界鱼群倾向由外貌雷同的本种个体组成较大团体——形差者会成为捕食者首要目标,这驱动了整个团体的同质化。当同速同向同步游动时,组织最严:同物种、同年龄层、保持定距,能做出似有共同头脑的复杂机动,如细长方阵中快速升降、绕轴旋转且互不碰撞,依赖极快的相对位置感应与响应。 数学建模把这种行为压成三条基本规则:与邻同速同向、与邻保持近距离、避免与邻碰撞。但团体游弋方向由谁定仍未解,迁移中似多数知情,小群中仍有更大胆的领导者。该行为已催生出解决优化问题的算法模型,值得在 MT5 里用群体逻辑跑一遍参数敏感性。 外汇与贵金属属高风险市场,任何借群体算法做的信号辅助都只具概率倾向,不能直接当方向凭据。
鱼群搜索怎么在MT5里落地
鱼群搜索(FSS)归在群体智能里的元启发式一类,2008年由 Bastos Filho 和 Lima Neto 提出,核心代理叫“鱼”,每条鱼带一个权重记录寻优“成功度”。权重同时充当记忆,这让 FSS 跟粒子群不同——它不需要显式维护全局最优,靠权重中心在迭代里自然漂移到更优点。 算法算子分两组:投喂算子算权重,游泳算子管迁移。投喂里权重增量正比于归一化适应度差:fishes[f].weight = fishes[f].weight + (fishes[f].delta_fitness / max_delta_fitness)。游泳又拆成个体、本能集体、集体意志三类,个体游弋是局部随机步,本能集体按上轮适应度变化同步平移鱼群,集体意志则看团体是否改善决定向质心聚或散。 原文作者把伪代码接进 MT5 时改了调用顺序:经典是个体→算适应度→本能→意志,但他用 Init 和 swimmingRegime 两个标志重排成 first_method→算适应度→second_method。Init() 里鱼结构清零,权重预设 1000.0,适应度填 -DBL_MAX,这样首轮差值为零。 步长参数他没照文献的 0.01 / 0.001,实测不好用,改成 initial_step_ind=0.1、initial_step_vol=0.8。喂养里若 max_delta_fitness 为零,他允许权重取负、直接令 weight=1,不强制适应度必须为正。开 MT5 把下面结构跑一遍,看权重中心是否随迭代往高适应度区挪。
class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="kw">struct S_Fish { class="type">void Init(class="type">int dimensions) { ArrayResize(current_position, dimensions); ArrayResize(new_position, dimensions); ArrayResize(delta_position, dimensions); weight = class="num">1000.0; fitness = -DBL_MAX; delta_fitness = class="num">0.0; }
「鱼群算法初始化与游泳调度的落地细节」
下面这段是鱼群搜索(FSS)类里最容易被忽略的两段代码:Init 负责把维度、鱼群规模、步长系数一次性落进数组;Swimming 则按 init 标志决定是冷启动随机撒点,还是进入个体/群体移动 regime。直接在 MT5 里建个 EA 把这两段粘进去,用 Print 打出 step_ind[0] 和 global_best,能立刻看到第一帧的搜索边界。 Init 的前几行先用 MathSrand(GetTickCount()) 播种随机,保证每次 EA 重启后鱼群初始分布不重复;随后把 dimensions、fishSchSizeP 写进成员变量,并用 ArrayResize 给 rangeMax / rangeMin / rangeStep / fishes 按维度或规模扩容。注意 total_weight 直接赋成 num_of_individuos,意味着初始阶段每条鱼权重相等,后续 volitive 移动才会按 fitness 重新分配。 Swimming 的冷启动分支里,step_ind[d] 和 step_vol[d] 都等于初始系数乘上该维搜索跨度 (rangeMax[d]-rangeMin[d]);鱼的位置 new_position[d] 先由 RNDfromCI 在连续区间随机,再经 SeInDiSp 吸附到离散网格——如果你的参数空间是手数或均线周期这类整数维,这步能避免浮点越界。 当 init 为 true 后,swimmingRegime==1 只跑个体移动,其余情况走本能集体移动+意志集体移动两段。实盘调参时建议先把 swimmingRegime 锁在 1,单看个体移动能否在 200 代内把 fitness 拉出 -DBL_MAX,再放开群体项,外汇与贵金属波动剧烈,此类优化仅作信号参考,实盘存在较高风险。
class="type">class="kw">double current_position []; class="type">class="kw">double new_position []; class="type">class="kw">double delta_position []; class="type">class="kw">double weight; class="type">class="kw">double fitness; class="type">class="kw">double new_fitness; class="type">class="kw">double delta_fitness; }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_FSS::Init(class="kw">const class="type">int dimensionsP, class="kw">const class="type">int fishSchSizeP, class="kw">const class="type">class="kw">double initial_step_indP, class="kw">const class="type">class="kw">double initial_step_volP) { MathSrand(GetTickCount()); init = class="kw">false; swimmingRegime = class="num">1; dimensions = dimensionsP; ArrayResize(rangeMax, dimensions); ArrayResize(rangeMin, dimensions); ArrayResize(rangeStep, dimensions); num_of_individuos = fishSchSizeP; ArrayResize(fishes, num_of_individuos); for (class="type">int i = class="num">0; i < num_of_individuos; i++) { fishes [i].Init(dimensions); } global_best = -DBL_MAX; ArrayResize(global_best_position, dimensions); total_weight = num_of_individuos; initial_step_ind = initial_step_indP; ArrayResize(step_ind, dimensions); initial_step_vol = initial_step_volP; ArrayResize(step_vol, dimensions); ArrayResize(collective_instinct, dimensions); ArrayResize(barycenter, dimensions); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_FSS::Swimming(class="type">int i) { class=class="str">"cmt">//---------------------------------------------------------------------------- if (!init) { global_best = -DBL_MAX; swimmingRegime = class="num">1; for (class="type">int d = class="num">0; d < dimensions; d++) { step_ind [d] = initial_step_ind * (rangeMax [d] - rangeMin [d]); step_vol [d] = initial_step_vol * (rangeMax [d] - rangeMin [d]); } for (class="type">int f = class="num">0; f < num_of_individuos; f++) { fishes [f].Init(dimensions); for (class="type">int d = class="num">0; d < dimensions; d++) { fishes [f].new_position [d] = RNDfromCI(rangeMin [d], rangeMax [d]); fishes [f].new_position [d] = SeInDiSp(fishes [f].new_position [d], rangeMin [d], rangeMax [d], rangeStep [d]); } } } class=class="str">"cmt">//---------------------------------------------------------------------------- else { class="kw">switch (swimmingRegime) { case class="num">1: apply_individual_movement(); class=class="str">"cmt">//individual movement class="kw">break; class="kw">default: apply_instintive_collective_movement(); class=class="str">"cmt">//instinctively-collective movement apply_collective_volitive_movement(); class=class="str">"cmt">//collective-volitional movement