种群优化算法:Nelder-Mead(NM),或单纯形搜索方法(基础篇)
📘

种群优化算法:Nelder-Mead(NM),或单纯形搜索方法(基础篇)

第 1/3 篇

用单纯形法给EA找参数

Nelder-Mead(NM)也叫单纯形搜索,是一类不依赖梯度的种群/几何优化算法,适合在 MT5 里替 EA 搜一组较优输入参数。它靠维护一个 simplex(多顶点几何体)不断反射、收缩来逼近极值,对目标函数不可导的场景比梯度法更稳。 在 MT5 上跑这类优化,常见做法是把策略参数空间交给 NM 搜索,而不是 brute-force 网格遍历。社区里一篇 2024-06-21 发布的示例帖,截至统计有 1359 次查看、1 条评论,说明这类轻量优化器在实战圈有真实需求。 外汇与贵金属杠杆高、滑点跳空频繁,任何参数优化结果都只是历史样本上的概率倾向,实盘前务必用 MT5 策略测试器做样本外验证。

◍ 不用导数的单纯形优化是怎么来的

Nelder-Mead 方法由 John Nelder 和 Roger Mead 在 1965 年提出,初衷很直接:当时他们想找一种能跑在没有导数、甚至没有导数解析方程的函数上的优化办法。传统梯度法在这种函数面前基本失效,而他们希望算法既好实现又能在那个年代的计算机上跑得动。 思路灵感来自单纯形——也就是函数参数空间里的一个多面体。单纯形由一组顶点构成,每个顶点对应一组待优化的参数值;算法靠在参数空间里不断变形、移动这个多面体,去逼近函数最优值。原文载于《计算机杂志》的《函数最小化的单纯形方法》一文,后来被各领域拿去处理无梯度优化问题。 从类别看,它属于无条件优化算法,判定性、不需要函数导数,也能应付带多个局部最小值的函数。对做 MT5 上复杂目标函数寻优(比如多参数 EA 调参)的交易者来说,这类算法意味着你不用自己推导梯度,也能搜到可用参数组合。外汇与贵金属杠杆交易高风险,参数优化结果仅代表历史样本上的概率倾向,实盘可能失效。

「单纯形怎么在参数空间里翻跟头」

Nelder-Mead 不是群体算法那一套,它只养一个活体,活体里塞一个单纯形,单纯形由 z+1 个顶点撑起(z 是维度)。我们这边为了并行探索,给每个智能体各发一个单纯形,于是整体看着像群体,骨子里还是单活体迭代。 维度 z=2 时单纯形有 3 个顶点,按目标函数排成「最好 / 好 / 最差」。每轮先算去掉最差点的质心 Xo,再把最差点相对质心做反射:Xr = Xo + α(Xo - Xw),α 默认 1。反射若比最佳还好就扩张 Xe = Xo + γ(Xr - Xo),γ 默认 2;若反射比「好」点还差就收缩 Xc = Xo + β(Xw - Xo),β 默认 0.5。 γ 和 β 都不能乱填。γ 太大单纯形会飞出有效区、丢局部极值信息;太小探不动。β 太大把单纯形压扁同样丢信息,太小又缩不到最差点附近。原文作者还列了第 4 个操作「缩窄」,要把所有点向最佳偏移并重算一遍适应度——维度 1000 时得再算 1000 次,纯烧 CPU 且容易全体陷进一个局部极值,我在实现里直接砍了这步。 真正麻烦的是局部陷阱。单纯形顶点每次只挪固定量,像踩高跷坐矮凳,初始顶点没落在好位置就废了。交易优化面满是离散坑,光滑抛物线才适合它。我的补丁是接 Levy 航班:卡住时让顶点按偏向全局最佳的概率分布随机跳走,可能把活体踢出陷阱,但不保证每次都有用,纯实验性质。 代码层用 E_SimplexOperation 枚举记每轮对最差点干了啥(none/reflection/expansion/contraction)。S_Point 存坐标数组 c[] 和适应度 f;S_Simplex 聚合顶点、质心、Xr/Xe/Xc 及好坏点索引;S_Agent 是每个活体的壳,挂一个单纯形。C_AO_NMm 类管群体、范围与比率,Init 布数组,Revision 跑主逻辑,CalcCentroid/Reflection/Expansion/Contraction/Flying 各管一摊。

让活体沿全局最优飞出去

在 Flying 方法里,先给 agent.s.operation 置为 reflection,算法迭代就会走反射后的后续逻辑,而不是重算一遍反射。接着把 SeInDiSp 套到 Xr 上,用 rangeMin[c]、rangeMax[c] 和 rangeStep[c] 三个数组约束它的取值边界与步长,结果写回 agent.s.Xr.c[c],保证坐标始终落在有效搜索区间内。 所谓“飞行”,本质是活体依据全局最佳解的当前坐标,叠加一个随机值去改写自身坐标。这种扰动让活体有可能跳出当前聚簇,探到解空间里还没被扫过的区域,从而有概率找到更优解。 下面这段结构体定义是上面逻辑在 MT5 里的落地骨架。E_SimplexOperation 枚举了四种单纯形操作,none 到 contraction 分别对应 0–3;S_Point 存单点坐标与适应度,初始化时把 f 设成 -DBL_MAX 表示尚未评估。 S_Simplex 里 p[] 比维数多 1 个点,c[] 是质心,Xr/Xe/Xc 分别是反射、扩张、收缩点,indG/indW 记好坏点下标,operation 控制本步动作。S_Agent 在单纯形外再包一层坐标与适应度,C_AO_NMm 则持有全局最优 cB/fB、种群 a[] 以及三套区间数组 rangeMin/Max/Step——开 MT5 新建 EA 把这些 struct 贴进去,就能直接调 Init 验证边界约束是否生效。

MQL5 / C++
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
enum E_SimplexOperation
{
  none        = class="num">0,
  reflection  = class="num">1,
  expansion   = class="num">2,
  contraction = class="num">3
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="kw">struct S_Point
{
  class="type">void Init(class="type">int coords)
  {
    ArrayResize(c, coords);
    f = -DBL_MAX;
  }
  class="type">class="kw">double c [];
  class="type">class="kw">double f;
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="kw">struct S_Simplex
{
  class="type">void Init(class="type">int coords)
  {
    ArrayResize(p, coords + class="num">1);
    for (class="type">int i = class="num">0; i <= coords; i++)
    {
      p [i].Init(coords);
    }
    ArrayResize(c, coords);
    Xr.Init(coords);
    Xe.Init(coords);
    Xc.Init(coords);
    operation = none;
  }
  S_Point p [];
  class="type">class="kw">double c  []; class=class="str">"cmt">//centroid
  S_Point Xr;  class=class="str">"cmt">//reflection point
  S_Point Xe;  class=class="str">"cmt">//expansion point
  S_Point Xc;  class=class="str">"cmt">//expansion point
  class="type">int indG;    class=class="str">"cmt">//index of good point
  class="type">int indW;    class=class="str">"cmt">//index of worst point
  E_SimplexOperation operation; class=class="str">"cmt">//type of simplex operation
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="kw">struct S_Agent
{
  class="type">void Init(class="type">int coords)
  {
    ArrayResize(c, coords);
    f = -DBL_MAX;
    s.Init(coords);
  }
  class="type">class="kw">double c  []; class=class="str">"cmt">//coordinates
  class="type">class="kw">double f;     class=class="str">"cmt">//fitness
  S_Simplex s;  class=class="str">"cmt">//agent simplex
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class C_AO_NMm
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="kw">public: class="type">class="kw">double cB [];  class=class="str">"cmt">//best coordinates
  class="kw">public: class="type">class="kw">double fB;     class=class="str">"cmt">//FF of the best coordinates
  class="kw">public: S_Agent a [];  class=class="str">"cmt">//agent
  class="kw">public: class="type">class="kw">double rangeMax   []; class=class="str">"cmt">//maximum search range
  class="kw">public: class="type">class="kw">double rangeMin   []; class=class="str">"cmt">//manimum search range
  class="kw">public: class="type">class="kw">double rangeStep []; class=class="str">"cmt">//step search
  class="kw">public: class="type">void Init(const class="type">int       coordsP,                class=class="str">"cmt">//coordinates number
                                const class="type">int     popSizeP,              class=class="str">"cmt">//population size

常见问题

可以用单纯形法(Nelder-Mead)在参数空间里靠顶点比较自动翻找较优组合,不必算导数,适合黑箱回测目标。
它靠反射、扩张、收缩、压缩四个动作不断挪动最差顶点,像 simplex 在多维空间里打滚逼近低值区。
小布可接管重复寻参流程,你给目标与边界,它按单纯形逻辑批量回测并标出较优参数组合。
在边界内随机撒几组差异化初始点,或拉大初始边长,能让活体更倾向飞向全局较优区。
过度拟合概率偏高,贵金属与外汇波动随机性强,须跨周期验证并保留样本外数据再上实盘。