ALGLIB库优化方法(第一部分)·综合运用
⚙️

ALGLIB库优化方法(第一部分)·综合运用

(3/3)· 从BLEIC、L-BFGS到NS,前两部分拆完算法,这一篇只讲怎么在EA里真正跑通组合优化

实战向进阶 第 3/3 篇
很多人把ALGLIB三个优化器当孤立函数调,结果箱约束和线性约束混写直接报异常。其实NS求解器能吞下前两类约束的叠加场景,只是调用顺序和梯度开关容易配反。先把约束类型在纸上列清楚,再选求解器,比盲目试错省一半编译时间。

NS算法与AGS求解器的实战约束

NS(非光滑非凸优化)专门处理目标函数带断裂、跳跃且非凸的问题,典型场景是函数地形里同时存在多个局部极小和‘山谷’。它靠梯度采样绕开不可导点,每一步在当前解附近随机取点估梯度,再落成受限二次规划求移动方向,迭代更新。 AGS自适应梯度采样求解器是NS的核心,支持箱型、线性和非线性非光滑三类约束,还内置数值微分与变量缩放。但它有明显短板:不是为高维设计的——每步约做 2·N 次梯度评估,收敛常需 O(N) 次迭代,整轮优化就是 O(N²) 次估计,比起 L-BFGS 每步 O(1) 重很多。 边界变量必须用 CRowDouble 而非 double,且需包一层继承 CNDimensional_FVec 的包装类。虚方法改成 FVec 后,注意不能返回 DBL_MAX 当适应度,否则直接报错;正确做法是额外维护字段 fW 记录最差解并取负返回,停止调用也换成 MinNSRequestTermination。 跑抛物面测试时,设最大运行次数 10000 却实际调了 1006503 次,说明该参数对 NS 没生效。微分步长也敏感:用 1e-16 过早停滞,仅 96378 次调用就卡在 0.67849;而默认 1e-5 配 radius=0.8、rho=50 能正常迭代。外汇/贵金属参数寻优用 NS 须警惕高维下的百万级调用与终端假死风险。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span> ()
{
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Initialization of optimization parameters---------------------------------------</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span> numbTestFuncRuns = <span class="number">class="num">10000</span>;
&nbsp;&nbsp;<span class="keyword">class="type">int</span> params&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">1000</span>;
<span style="class="type">color:rgb(class="num">153, class="num">153, class="num">153);">class=class="str">"cmt">// Additionally, you need to specify --------------</span>
&nbsp;&nbsp;<span style="background-class="type">color:rgb(class="num">249, class="num">204, class="num">202);">CRowDouble</span> rangeMin, rangeMax;
&nbsp;&nbsp;rangeMin.Resize(params);
&nbsp;&nbsp;rangeMax.Resize(params);
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> rangeStep;
&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; params; i++)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;rangeMin.Set(i, -<span class="number">class="num">10</span>);
&nbsp;&nbsp;&nbsp;&nbsp;rangeMax.Set(i,&nbsp;&nbsp;<span class="number">class="num">10</span>);
&nbsp;&nbsp;}
&nbsp;&nbsp;rangeStep = <span class="macro">DBL_EPSILON</span>;
&nbsp;&nbsp;<span style="background-class="type">color:rgb(class="num">249, class="num">204, class="num">202);">CRowDouble</span> x, s;
x.Resize(params);
&nbsp;&nbsp;s.Resize(params);
&nbsp;&nbsp;s.Fill(<span class="number">class="num">1</span>);
<span style="class="type">color:rgb(class="num">153, class="num">153, class="num">153);">
class=class="str">"cmt">// Generate random initial parameter values in given ranges----</span>
&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; params; i++)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;x.Set(i, rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * <span class="functions">rand</span> () / <span class="number">class="num">32767.0</span>));
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Create objects for optimization------------------------------------------</span>
&nbsp;&nbsp;C_OptimizedFunction fFunc; fFunc.Init(params, numbTestFuncRuns);
&nbsp;&nbsp;CObject&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj;
&nbsp;&nbsp;CNDimensional_Rep&nbsp;&nbsp; frep;
&nbsp;&nbsp;CMinNSReport&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rep;
<span style="class="type">color:rgb(class="num">153, class="num">153, class="num">153);">
class=class="str">"cmt">// Set the parameters of the NS optimization algorithm------------------------------</span>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> diffStep = <span class="number">class="num">0.00001</span>;
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">radius</span>&nbsp;&nbsp; = <span class="number">class="num">0.8</span>;
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">rho</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">50.0</span>;
&nbsp;&nbsp;CAlglib::MinNSCreateF&nbsp;&nbsp;&nbsp;&nbsp;(x, diffStep, fFunc.state);
&nbsp;&nbsp;CAlglib::MinNSSetBC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(fFunc.state, rangeMin, rangeMax);
&nbsp;&nbsp;CAlglib::MinNSSetScale&nbsp;&nbsp; (fFunc.state, s);
&nbsp;&nbsp;CAlglib::MinNSSetCond&nbsp;&nbsp;&nbsp;&nbsp;(fFunc.state, rangeStep, numbTestFuncRuns);
&nbsp;&nbsp;<span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);">CAlglib::MinNSSetAlgoAGS</span> (fFunc.state, <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">radius</span>, <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">rho</span>);
&nbsp;&nbsp;CAlglib::MinNSOptimize&nbsp;&nbsp; (fFunc.state, fFunc, frep, obj);
&nbsp;&nbsp;CAlglib::MinNSResults&nbsp;&nbsp;&nbsp;&nbsp;(fFunc.state, x, rep);
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Output of optimization results-----------------------------------------------</span>
&nbsp;&nbsp;<span class="functions">Print</span> (<span class="class="type">class="kw">string">"NS, best result: "</span>, fFunc.fB, <span class="class="type">class="kw">string">", number of function launches: "</span>, fFunc.numberLaunches);
}
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span><span style="class="type">color:rgb(class="num">153, class="num">153, class="num">153);">
class=class="str">"cmt">// Class for function optimization, inherits from CNDimensional_FVec
</span><span class="keyword">class</span> C_OptimizedFunction : <span class="keyword">class="kw">public</span> <span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);">CNDimensional_FVec</span>
{

◍ 优化外壳如何接管目标函数调用

在 MT5 里做参数寻优时,真正被算法反复调用的不是你的策略主逻辑,而是一个继承自 C_OptimizedFunction 的虚函数 FVec。它每次拿到一组坐标 x,算完目标函数后把结果塞进 fi,顺便维护最好 / 最差解的轨迹。 Init 负责把 Launch 计数器归零、设定最大调用次数 maxNumberLaunchesAllowed,并用 ArrayResize 按 coords 维度开好当前坐标数组 c 与最优坐标数组 cB;fB 初始化为 -DBL_MAX(记录最大目标值),fW 初始化为 DBL_MAX(记录最差目标值)。 FVec 第一行就 numberLaunches++,一旦超过允许次数,直接 fi.Set(0, -fW) 并调用 MinNSRequestTermination(state) 终止 Alglib 的 Nelder-Mead 搜索——注意这里返回的是 -fW 而非注释里写的 DBL_MAX,避免优化器收到非法极值。 正常分支里,坐标拷进 c 后跑 ObjectiveFunction(c),把 -ffVal 写回 fi(因为 Alglib 默认求最小,所以取负把“求最大”转成“求最小”);随后用 ffVal 刷新 fW 与 fB,最优时 ArrayCopy(cB, c) 留存坐标。外汇与贵金属品种波动剧烈,这套寻优在外挂实盘前务必用历史数据多轮回测,过拟合概率偏高。 开 MT5 把这段直接贴进 EA 的优化类,改 ObjectiveFunction 内部为你自己的夏普或回撤指标,就能跑通一套不带源码暴露的闭包寻优。

MQL5 / C++
 class="kw">public: class=class="str">"cmt">//--------------------------------------------------------------------
  C_OptimizedFunction(class="type">void) { }
  ~C_OptimizedFunction(class="type">void) { }
class=class="str">"cmt">// A class="kw">virtual function to contain the function being optimized--------
  class="kw">virtual class="type">void FVec(CRowDouble &x, CRowDouble &fi, CObject &obj);
class=class="str">"cmt">// Initialization of optimization parameters---------------------------------------
  class="type">void Init(class="type">int coords,
             class="type">int maxNumberLaunchesAllowed)
  {
    numberLaunches       = class="num">0;
    maxNumbLaunchesAllowed = maxNumberLaunchesAllowed;
    fB = -DBL_MAX;
    fW =  DBL_MAX;
    ArrayResize(c,  coords);
    ArrayResize(cB, coords);
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  CMinNSState state;             class=class="str">"cmt">// State 
  class="type">int       numberLaunches;      class=class="str">"cmt">// Launch counter
  class="type">class="kw">double fB;                    class=class="str">"cmt">// Best found value of the objective function(maximum)
  class="type">class="kw">double fW;                    class=class="str">"cmt">// Worst found value of the objective function(maximum)
  class="type">class="kw">double cB [];                 class=class="str">"cmt">// Coordinates of the point with the best function value
  class="kw">private: class=class="str">"cmt">//--------------------------------------------------------------
  class="type">class="kw">double c  [];                 class=class="str">"cmt">// Array for storing current coordinates
  class="type">int   maxNumbLaunchesAllowed; class=class="str">"cmt">// Maximum number of function calls allowed
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_OptimizedFunction::FVec(CRowDouble &x, CRowDouble &fi, CObject &obj)
{
  class=class="str">"cmt">// Increase the function launch counter and limitation control---------------
  numberLaunches++;
  if (numberLaunches >= maxNumbLaunchesAllowed)
  {
    class=class="str">"cmt">//fi.Set(class="num">0, DBL_MAX);  //Cannot class="kw">return DBL_MAX value
    fi.Set(class="num">0, -fW);
    CAlglib::MinNSRequestTermination(state);
    class="kw">return;
  }
  class=class="str">"cmt">// Copy input coordinates to internal array-------------------------
  for (class="type">int i = class="num">0; i < x.Size(); i++) c [i] = x [i];
  class=class="str">"cmt">// Calculate objective function value----------------------------------------
  class="type">class="kw">double ffVal = ObjectiveFunction(c);
  fi.Set(class="num">0, -ffVal);
  class=class="str">"cmt">// Update the best and worst solutions found----------------------------
  if (ffVal < fW) fW = ffVal;
  if (ffVal > fB)
  {
    fB = ffVal;
    ArrayCopy(cB, c);
  }
}

「一点提醒」

这套 ALGLIB 优化示例已经把 BLEIC、L-BFGS 和 NS 三个算法的可直接跑的脚本都给了出来,压缩包里三个 .mq5 文件分别对应 1 个测试脚本,合计 4.36 KB,开 MT5 拖进去就能看目标函数被调了多少次。 前面几节聊到的「调用次数失控」不是吓人,BLEIC 在约束边界抖动点位时,实测函数求值次数会随维度线性往上窜,跑之前最好先在脚本里把 maxits 写死。 外汇与贵金属参数优化本质是高杠杆高风险博弈,回测顺滑不代表实盘能复现,任何算法给出的都只是概率倾向。 文章里那句致谢和俄文原链只是出处注脚,真要落地,去作者 github 翻 Population-optimization-algorithms-MQL5 比反复读译文更省时间。

让小布替你跑这套
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到多约束优化后的参数敏感带,把重复劳动交给小布,你专注决策。

常见问题

步长通常取变量量程的1e-4到1e-6,过大易漏掉拐点,过小会放大浮点噪声;实盘前用历史切片做敏感性扫描再定。
小布盯盘的AIGC模块已封装常见约束优化诊断,打开品种页可见参数敏感带与约束冲突提示,不需自己写MQL5胶水层。
平滑凸问题下两者极小值接近,但L-BFGS内存占用低、收敛快;非凸地形里BLEIC的边界处理更不易越界,概率上更稳。
ALGLIB数值法需变量连续可采样,若目标函数含离散分支(如手数取整)会断梯度,需用NS或外层包装连续松弛。
关于约束建模的完整讨论见《ALGLIB库优化方法(第一部分)·基础篇》。