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

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

(3/3)· 盒式、非线性约束与 LM 三类算法同台压测,给出按任务选型的实战判断依据

案例拆解 第 3/3 篇
很多人把 ALGLIB 的优化器当成黑箱,随机试一个就用到底,结果在带约束的多维面上收敛慢或半路卡死。其实 BC、NLC、LM 各有明确的适用边界,跑前先看函数形状和约束类型能省掉大量无效计算。

用三类测试函数给优化器跑分

选优化器不能拍脑袋。把现实里常见的三类难度塞进测试函数:Hilly 代表平滑曲面,Forest 带尖锐不可微顶点,Megacity 是纯离散问题。每组跑 50 次、目标函数调用上限锁死在 10,000 次,才能横向比出谁更扛造。 以 BLEIC 为例,它在 5 个 Hilly 上平均用掉 2178 次调用、结果 0.3848;到了 500 个 Megacity 变量时调用飙到 15147 次(已超上限截断),结果 0.0979。六种方法里 NS(非光滑非凸)总分 2.09251 居首,LM(改进 Levenberg-Marquardt)仅 1.54204 垫底,差距约 5.5 个百分点。 LM 的提前终止很典型:10 参数的 Megacity 它 21 次迭代就停了,1000 参数也才 2081 次,远没碰 10,000 上限;而 NS 在 500 变量问题上直接干到 1,006,503 次调用。说明梯度类方法在离散/非光滑域容易过早收敛,外汇参数寻优若涉及离散手数档位要当心这类坑。 开 MT5 把下面 FuncTests 骨架填上 ALGLIB 调用,自己换测试函数名就能复现上面的打印格式。收敛图一画,哪种方法在你的品种周期上更稳一目了然。

◍ 函数批量测试的骨架怎么搭

在 MT5 里做指标或策略函数的横向比对,先得有一个能循环吃参数的测试入口。下面这段代码展示了一个 FuncTests 函数的标准形态:传入函数对象引用、待测数量、绘图色、累计得分与总测试数引用,内部自行决定是否可视化。 当 funcCount 小于等于 0 时直接 return,避免空跑;随后 allTests 自增 1,标记本轮测试已计入。若 Video_P 开关为真,则调用 ST 系列方法把函数曲线画到画布上并刷新,方便肉眼看形态。 真正干活前会初始化一组变量:xConv / yConv 管坐标轴换算,aveResult 存平均结果,aveRunsFF 记平均调用次数。params 按 funcCount * 2 算(每个函数 2 个参数),epochCount 则用 NumbTestFuncRuns_P / PopSize_P 得出迭代轮数——这两个宏你要在自己的 EA 里先定义好,否则编译不过。 开 MT5 新建一个 mq5,把 FuncTests 抄进去,顺手给 NumbTestFuncRuns_P 设成 1000、PopSize_P 设成 50,就能看到 epochCount 落为 20,外汇与贵金属测试请务必认清这是高波动高风险环境,结果仅代表历史样本概率。

MQL5 / C++
class="type">void FuncTests(C_Function&nbsp;&nbsp;&nbsp;&nbsp;&amp;f,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Reference to the target function object</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">const</span>&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp; funcCount,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment"> class=class="str">"cmt">// Number of functions to test</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">const</span>&nbsp;&nbsp;class="type">class="kw">color&nbsp;&nbsp; clrConv,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment"> class=class="str">"cmt">// Visualization class="type">class="kw">color</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;allScore,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"> class=class="str">"cmt">// Total score of all tests</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;allTests)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"> class=class="str">"cmt">// Total number of tests</span>
{
&nbsp;&nbsp;<span class="keyword">if</span> (funcCount &lt;= <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">  class=class="str">"cmt">// If the number of functions = class="num">0, exit the function</span>
&nbsp;&nbsp;allTests++;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment"> class=class="str">"cmt">// Increase the total number of tests</span>
&nbsp;&nbsp;<span class="keyword">if</span> (Video_P)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"> class=class="str">"cmt">// If visualization is enabled</span>
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;ST.DrawFunctionGraph(f);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment"> class=class="str">"cmt">// Draw the function graph</span>
&nbsp;&nbsp;&nbsp;&nbsp;ST.SendGraphToCanvas();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Send the graph to the canvas</span>
&nbsp;&nbsp;&nbsp;&nbsp;ST.MaxMinDr&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(f);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment"> class=class="str">"cmt">// Determine the maximum and minimum of the function</span>
&nbsp;&nbsp;&nbsp;&nbsp;ST.Update&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Update the visualization</span>
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//----------------------------------------------------------------------------</span>
&nbsp;&nbsp;C_AO_Utilities Ut;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"> class=class="str">"cmt">// Utilities for handling numbers</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;xConv&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">0.0</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"> class=class="str">"cmt">// Variable for converting along the X axis</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;yConv&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">0.0</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"> class=class="str">"cmt">// Variable for converting along the Y axis</span>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> aveResult&nbsp;&nbsp;= <span class="number">class="num">0.0</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"> class=class="str">"cmt">// Average test result</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;aveRunsFF&nbsp;&nbsp;= <span class="number">class="num">0</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"> class=class="str">"cmt">// Average number of function runs</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">params</span>&nbsp;&nbsp;&nbsp;&nbsp; = funcCount * <span class="number">class="num">2</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">  class=class="str">"cmt">// Number of parameters(class="num">2 for each function)</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;epochCount = NumbTestFuncRuns_P / PopSize_P; <span class="comment">class=class="str">"cmt">// Number of epochs</span>

「参数边界与随机采样初始化」

做多函数并行优化时,先给每个目标函数的 X、Y 维度分别建上下界数组。代码里用 bndl 存下界、bndu 存上界,循环里按 i*2 和 i*2+1 交错写入,保证第 i 个函数的 X 边界占索引 2i、Y 边界占 2i+1。 缩放数组 s 直接 Fill(1),意味着初始阶段不对参数做归一化压缩,真实量纲直接进优化器。若你的价格数据跨度极大(比如跨品种波动率差 10 倍),这一步可能要手动改缩放系数。 随机测试外层跑 NumberRepetTest_P 次,内层对 funcCount 个函数各抽一组 (X,Y):Ut.RNDfromCI 在闭区间均匀取样,写回 x 数组对应位置。这样每次重复测试拿到的都是独立随机起点,能粗略看出优化器对初值的敏感度。 最小化状态机用了 epsg=1e-16 与 epsf=1e-16 双停止阈值,梯度与函数值变化都小于该量才停。外汇与贵金属回测中这种极严阈值可能让迭代次数飙升,实盘前建议在 MT5 里先调成 1e-8 量级看耗时差异。

MQL5 / C++
CRowDouble bndl; bndl.Resize(params);                 class=class="str">"cmt">// Array for lower bounds
CRowDouble bndu; bndu.Resize(params);                 class=class="str">"cmt">// Array for upper bounds
for (class="type">int i = class="num">0; i < funcCount; i++)                   class=class="str">"cmt">// Fill the boundaries for each function
{
   bndu.Set(i * class="num">2, f.GetMaxRangeX());               class=class="str">"cmt">// Set the upper boundary by X
   bndl.Set(i * class="num">2, f.GetMinRangeX());               class=class="str">"cmt">// Set the lower boundary by X
   bndu.Set(i * class="num">2 + class="num">1, f.GetMaxRangeY());           class=class="str">"cmt">// Set the upper bound by Y
   bndl.Set(i * class="num">2 + class="num">1, f.GetMinRangeY());           class=class="str">"cmt">// Set the lower boundary by Y
}
CRowDouble x; x.Resize(params);                      class=class="str">"cmt">// Array for parameter values
CRowDouble s; s.Resize(params);                      class=class="str">"cmt">// Array for scaling
s.Fill(class="num">1);                                           class=class="str">"cmt">// Fill the array with ones
for (class="type">int test = class="num">0; test < NumberRepetTest_P; test++) class=class="str">"cmt">// Run the tests
{
   for (class="type">int i = class="num">0; i < funcCount; i++)               class=class="str">"cmt">// For each function
   {
      x.Set(i * class="num">2,     Ut.RNDfromCI(bndl [i * class="num">2],     bndu [i * class="num">2]));     class=class="str">"cmt">// Generate random values by X
      x.Set(i * class="num">2 + class="num">1, Ut.RNDfromCI(bndl [i * class="num">2 + class="num">1], bndu [i * class="num">2 + class="num">1])); class=class="str">"cmt">// Generate random values by Y
   }
   class=class="str">"cmt">//--------------------------------------------------------------------------
   CObject                obj;                                                 class=class="str">"cmt">// Object for storing results
   C_OptimizedFunction fFunc; fFunc.Init(params, NumbTestFuncRuns_P, PopSize_P, clrConv); class=class="str">"cmt">// Initialize the optimization function
   CNDimensional_Rep      frep;                                                class=class="str">"cmt">// Representation of multidimensional space
   CMinBCState            state;                                               class=class="str">"cmt">// State of the minimization method
   CMinBCReport           rep;                                                 class=class="str">"cmt">// Minimization report
   class="type">class="kw">double epsg = class="num">1e-16;                                                        class=class="str">"cmt">// Parameter for gradient stop condition
   class="type">class="kw">double epsf = class="num">1e-16;                                                        class=class="str">"cmt">// Parameter for the stop condition by function value

把边界约束优化跑完并汇总平均分

上面这段是 ALGLIB 一维有界最小化(MinBC)在 MT5 里的标准调用链。先建状态、设边界与缩放,再给终止条件(epsg/epsf/步长/最大函数评估次数),最后跑优化并取结果。 CAlglib::MinBCCreateF(x, DiffStep_P, state); // 创建最小化状态,传入初值 x 与差分步长 CAlglib::MinBCSetBC(state, bndl, bndu); // 设置下界 bndl 与上界 bndu CAlglib::MinBCSetScale(state, s); // 设置变量缩放,避免量纲差异拖慢收敛 CAlglib::MinBCSetCond(state, epsg, epsf, ArgumentStep_P, NumbTestFuncRuns_P); // 设梯度/函数值容差与最大评估次数 CAlglib::MinBCOptimize(state, fFunc, frep, obj); // 执行优化,fFunc 为目標函数 CAlglib::MinBCResults(state, x, rep); // 取最优 x 与报告 rep 循环体内把每次的函数调用次数 numberLaunches 和找到的最佳极小值 bestMIN 累加;循环结束后除以 NumberRepetTest_P(重复测试次数)得到平均调用次数 aveRunsFF 与平均结果 aveResult。 aveRunsFF /= NumberRepetTest_P; // 平均函数评估次数 aveResult /= NumberRepetTest_P; // 平均最优值 以 aveResult 作为单函数得分 score,Print 输出函数名、平均调用次数(括号内为设定上限 NumbTestFuncRuns_P)与平均结果,再把 score 累进 allScore。外汇与贵金属参数寻优高风险,回测平均分高只代表该样本集上收敛效率倾向更优,实盘可能失效。

MQL5 / C++
CAlglib::MinBCCreateF(x, DiffStep_P, state); class=class="str">"cmt">// Create minimization state
CAlglib::MinBCSetBC(state, bndl, bndu); class=class="str">"cmt">// Set the boundaries
CAlglib::MinBCSetScale(state, s);          class=class="str">"cmt">// Set scaling 
CAlglib::MinBCSetCond(state, epsg, epsf, ArgumentStep_P, NumbTestFuncRuns_P); class=class="str">"cmt">// Set conditions
CAlglib::MinBCOptimize(state, fFunc, frep, obj); class=class="str">"cmt">// Optimization
CAlglib::MinBCResults(state, x, rep);    class=class="str">"cmt">// Get results
class=class="str">"cmt">//--------------------------------------------------------------------------
aveRunsFF += fFunc.numberLaunches; class=class="str">"cmt">// Sum up the number of function runs
aveResult += -fFunc.bestMIN;      class=class="str">"cmt">// Sum up the best minimum found
}
aveRunsFF /= NumberRepetTest_P;   class=class="str">"cmt">// Calculate the average number of runs
aveResult /= NumberRepetTest_P;   class=class="str">"cmt">// Calculate the average result
class="type">class="kw">double score = aveResult;         class=class="str">"cmt">// Estimate based on average result
Print(funcCount, " ", f.GetFuncName(), "&class="macro">#x27;s; Func runs: ", aveRunsFF, "(", NumbTestFuncRuns_P, "); result: ", aveResult); class=class="str">"cmt">// Output test results
allScore += score;                class=class="str">"cmt">// Update the overall score
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

◍ 不同维度下算法选型的实测结论

低维平滑的 Hilly 函数上,NLC 方法给出了最优解;维度拉高后,LM 方法反超,成为高维平滑场景的首选。 尖锐极值类的 Forest 函数表现相反:低维时 NS 占优,高维则 BC 领跑。离散的 Megacity 问题里,小维度 NS 领先,大维度 BLEIC 排第一。 各方法偏差都落在自身结果区间内,差异不显著,但 NS 因不依赖梯度、适应面宽,可视为更通用的备选——代价是无法强制终止,得靠外部计数停。 随文附了 16 个文件:13 个脚本(如 Test_minNS.mq5 跑 NS 测试台)加 3 个 mqh 库(TestFunctions.mqh 装测试函数)。下载 ZIP 约 59 KB,在 MT5 里直接挂脚本就能复跑三套基准函数,自己看收敛曲线。

把重复压测交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多算法收敛对比的自动跑批结果,你只需在候选里挑最稳的那条曲线。

常见问题

当问题规模大且解中存在多个活跃盒式约束时,BC 的约束激活策略比 BLEIC 更轻,可能收敛得更迅速;小规模或约束稀疏时差异不大。
可以,小布内置的 AIGC 诊断会把 BC、NLC、LM 等方法的收敛过程和最终目标值做并列展示,省去手写脚本跑批的麻烦。
LM 本身面向最小二乘类无约束或简单约束问题,遇到硬盒式或非线性约束时倾向改用 BC 或 NLC,否则可能卡在不可行区。
这类方法结果完全由初值决定,在多维非凸面上不同起点可能落到不同局部极小,实操中常用随机初值采样来摸全貌。
历史面收敛好不代表样本外有效,贵金属与外汇杠杆高、跳空频繁,优化结果仅代表概率倾向,实盘前务必做前向验证。