ALGLIB库优化方法(第一部分)·综合运用
(3/3)· 从BLEIC、L-BFGS到NS,前两部分拆完算法,这一篇只讲怎么在EA里真正跑通组合优化
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 须警惕高维下的百万级调用与终端假死风险。
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="keyword">class="type">void</span> <span class="functions">OnStart</span> () { <span class="comment">class=class="str">"cmt">// Initialization of optimization parameters---------------------------------------</span> <span class="keyword">class="type">int</span> numbTestFuncRuns = <span class="number">class="num">10000</span>; <span class="keyword">class="type">int</span> params = <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> <span style="background-class="type">color:rgb(class="num">249, class="num">204, class="num">202);">CRowDouble</span> rangeMin, rangeMax; rangeMin.Resize(params); rangeMax.Resize(params); <span class="keyword">class="type">class="kw">double</span> rangeStep; <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < params; i++) { rangeMin.Set(i, -<span class="number">class="num">10</span>); rangeMax.Set(i, <span class="number">class="num">10</span>); } rangeStep = <span class="macro">DBL_EPSILON</span>; <span style="background-class="type">color:rgb(class="num">249, class="num">204, class="num">202);">CRowDouble</span> x, s; x.Resize(params); s.Resize(params); 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> <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < params; i++) { x.Set(i, rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * <span class="functions">rand</span> () / <span class="number">class="num">32767.0</span>)); } <span class="comment">class=class="str">"cmt">// Create objects for optimization------------------------------------------</span> C_OptimizedFunction fFunc; fFunc.Init(params, numbTestFuncRuns); CObject obj; CNDimensional_Rep frep; CMinNSReport 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> <span class="keyword">class="type">class="kw">double</span> diffStep = <span class="number">class="num">0.00001</span>; <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> = <span class="number">class="num">0.8</span>; <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> = <span class="number">class="num">50.0</span>; CAlglib::MinNSCreateF (x, diffStep, fFunc.state); CAlglib::MinNSSetBC (fFunc.state, rangeMin, rangeMax); CAlglib::MinNSSetScale (fFunc.state, s); CAlglib::MinNSSetCond (fFunc.state, rangeStep, numbTestFuncRuns); <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>); CAlglib::MinNSOptimize (fFunc.state, fFunc, frep, obj); CAlglib::MinNSResults (fFunc.state, x, rep); <span class="comment">class=class="str">"cmt">// Output of optimization results-----------------------------------------------</span> <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 内部为你自己的夏普或回撤指标,就能跑通一套不带源码暴露的闭包寻优。
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 比反复读译文更省时间。