头脑风暴优化算法(第二部分): 多模态(基础篇)
📘

头脑风暴优化算法(第二部分): 多模态(基础篇)

第 1/3 篇

◍ 多模态思路下的头脑风暴优化

头脑风暴优化(Brain Storm Optimization, BSO)在 MetaTrader 5 上的实现,第二篇重点转向多模态问题:传统 BSO 容易陷在单一全局峰,遇到多个等价优解的市场形态会过早收敛。 原文示例发布于 2024 年 10 月 31 日 09:06,截至统计有 565 次查看、4 条评论,说明这类算法改造在 MT5 圈子里有真实需求,不是纯论文玩具。 本小节只铺了三层骨架:1)问题背景,2)算法实现,3)测试结果;其中多模态改造的核心是在聚类后引入跨簇随机扰动,避免所有个体被拉向同一个质心。 想验证可行性,直接开 MT5 用 EA 向导挂一段聚类+扰动逻辑,先看回测曲线是否出现多个稳定 plateau,再谈参数调优。外汇与贵金属品种波动跳跃大,多模态优解不等于实盘稳健,请按高风险品种对待。

BSO 优化与聚类的配合逻辑

BSO(头脑风暴优化)把群体活动拆成观点生成与评估两个阶段,本质是在搜索空间里靠多智能体不断产出并筛选解。它和 K-Means、K-Means++ 这类聚类绑在一起用:聚类先把数据元素按相似度分组,让算法更快定位可能藏最优解的区域。 变异操作是另一块关键,它让 BSO 能跳过局部障碍、绕开搜索空间里的死胡同,从而走更短路径逼近最优。原文提到第一部分已拆解过这套逻辑结构,第二部分才进实盘代码与多模态测试。 对做 EA 参数寻优的交易者来说,这套组合的意义在于:单纯网格遍历容易卡在平坦区,而聚类+变异可能把回测耗时压下来。具体能省多少要看品种波动结构,外汇与贵金属属高风险品种,任何优化结果都只是历史样本上的概率倾向,不能直接当实盘保证。

「BSO智能体与种群类的骨架拆解」

BSO(头脑风暴优化)在MT5里落地,第一步是把每个候选解封装成结构体。下面这段S_BSO_Agent就是最小单元:c数组存坐标,f存适应度,label标聚类归属。 Init方法只接收一个coords参数,用ArrayResize把坐标数组拉到对应维度;f初始化成-DBL_MAX,也就是双精度负无穷,保证任何真实评价值都能覆盖它;label设-1表示还没进任何簇。创建新智能体时这三步必跑。 继承C_AO的C_AO_BSO类管整个种群。公开字段里popSize默认25、parentPopSize默认50,说明子代规模小于父代留存规模;clustersNumb、p_Replace、p_One、p_One_center、p_Two_center、k_Mutation、distribCoeff这组概率与比率,就是BSO区别于普通EA优化的核心旋钮。 Init里先调StandardInit做范围与步长校验,失败直接返false;随后agent数组Resize到popSize,clusters到clustersNumb,parents和parentsTemp都开到parentPopSize+popSize——多出来的空间就是给每轮并归父代与子代后排序留的。epochs按入参定,epochsNow从0起。 Moving方法每轮先epochsNow++,若revision为false就随机撒坐标然后退出;否则按u.RNDprobab()<p_Replace决定搬簇心,按<p_One决定从单簇还是双簇取观点,再做变异并用SeInDiSp夹紧到搜索范围。这套逻辑让智能体既跟簇心也跨簇探索。 Revision负责回写适应度、把新观点并回父代、按f排序只留优解、首次迭代用K-Means初始化聚类,并刷新各簇质心。原文作者实测BSO对明显局部极值探索不足,即便在FitnessDistance里加alpha权重调和距离与适应度,对K-Means质心更新改动后分配模式也没明显改善。 若K-Means系不给力,可换DBSCAN(密度聚类)、层次聚类、GMM或谱聚类;附带的类里直接替掉K-Means方法就能自测。外汇与贵金属参数优化属高风险实验,回测结论仅代表历史样本,实盘可能失效。

MQL5 / C++
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="kw">struct S_BSO_Agent
{
    class="type">class="kw">double c      []; class=class="str">"cmt">//coordinates
    class="type">class="kw">double f;        class=class="str">"cmt">//fitness
    class="type">int    label;    class=class="str">"cmt">//cluster membership label
    class="type">void Init(class="type">int coords)
    {
        ArrayResize(c,     coords);
        f      = -DBL_MAX;
        label = -class="num">1;
    }
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class C_AO_BSO : class="kw">public C_AO
{
  class="kw">public: class=class="str">"cmt">//--------------------------------------------------------------------
  ~C_AO_BSO() { }
  C_AO_BSO()
  {
    ao_name = "BSO";
    ao_desc = "Brain Storm Optimization";
    ao_link = "[MQL5官方文档]
    popSize          = class="num">25;   class=class="str">"cmt">//population size
    parentPopSize    = class="num">50;   class=class="str">"cmt">//parent population size;

◍ 遗传参数落进结构体再回灌

这段把聚类遗传算法的可调项先写死成默认常量,再塞进一个长度为 9 的 params 数组,靠 name+val 配对,方便外部读取或 UI 暴露。默认 clustersNumb=5、p_Replace=0.1、p_One=0.5、p_One_center=0.3、p_Two_center=0.2、k_Mutation=20.0、distribCoeff=1.0,这些数直接决定种群交叉与变异的偏向。 SetParams() 做反向操作:把 params 里下标 0~8 的 val 按类型强转回整型或浮点,重新赋给 popSize、parentPopSize、clustersNumb 等运行变量。也就是说,优化器跑完一轮若改了 params,调用一次 SetParams 就能让下一轮初始化吃进新值,不用动原常量。 Init() 的入参是三组等长数组 rangeMinP、rangeMaxP、rangeStepP,分别对应每个待优化维度的下界、上界和搜索步长。外汇与贵金属参数寻优高风险,这类边界设窄了容易陷局部最优,设宽了 MT5 回测可能跑成小时级,建议先拿 5 维小样本试水温。

MQL5 / C++
clustersNumb   = class="num">5;    class=class="str">"cmt">//number of clusters
p_Replace       = class="num">0.1;  class=class="str">"cmt">//replace probability
p_One           = class="num">0.5;  class=class="str">"cmt">//probability of choosing one
p_One_center    = class="num">0.3;  class=class="str">"cmt">//probability of choosing one center
p_Two_center    = class="num">0.2;  class=class="str">"cmt">//probability of choosing two centers
k_Mutation      = class="num">20.0; class=class="str">"cmt">//mutation coefficient
distribCoeff    = class="num">1.0;  class=class="str">"cmt">//distribution coefficient
ArrayResize(params, class="num">9);
params [class="num">0].name = "popSize";       params [class="num">0].val  = popSize;
params [class="num">1].name = "parentPopSize"; params [class="num">1].val  = parentPopSize;
params [class="num">2].name = "clustersNumb";  params [class="num">2].val  = clustersNumb;
params [class="num">3].name = "p_Replace";     params [class="num">3].val  = p_Replace;
params [class="num">4].name = "p_One";         params [class="num">4].val  = p_One;
params [class="num">5].name = "p_One_center";  params [class="num">5].val  = p_One_center;
params [class="num">6].name = "p_Two_center";  params [class="num">6].val  = p_Two_center;
params [class="num">7].name = "k_Mutation";    params [class="num">7].val  = k_Mutation;
params [class="num">8].name = "distribCoeff";  params [class="num">8].val  = distribCoeff;
}
class="type">void SetParams()
{
  popSize       = (class="type">int)params [class="num">0].val;
  parentPopSize = (class="type">int)params [class="num">1].val;
  clustersNumb  = (class="type">int)params [class="num">2].val;
  p_Replace     = params      [class="num">3].val;
  p_One         = params      [class="num">4].val;
  p_One_center  = params      [class="num">5].val;
  p_Two_center  = params      [class="num">6].val;
  k_Mutation    = params      [class="num">7].val;
  distribCoeff  = params      [class="num">8].val;
}
class="type">bool Init(class="kw">const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range
                     class="kw">const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">//maximum search range
                     class="kw">const class="type">class="kw">double &rangeStepP []) class=class="str">"cmt">//step search

常见问题

样本在解空间里明显分团、局部极值多时就先聚类再分群搜索;分布较均匀可跳过聚类直接跑全局,省算力。
智能体至少要有位置初始化和适应度评估,种群类要有生成、聚类、更新三步,缺一个迭代就会断。
小布可读取你的结构体配置,自动比对多组遗传参数回灌结果,标出收敛更快的组合,省去手动试错。
参数集中管理后改一处即可全种群生效,避免散落在函数里改漏,回灌时也能直接序列化复用。
先用历史行情跑离线收敛测试,确认聚类不频繁抖动、种群不早停,再谈接入实时决策。