ALGLIB 库优化方法(第二部分)·综合运用
(3/3)· 盒式、非线性约束与 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,外汇与贵金属测试请务必认清这是高波动高风险环境,结果仅代表历史样本概率。
class="type">void FuncTests(C_Function &f, <span class="comment">class=class="str">"cmt">// Reference to the target function object</span> <span class="keyword">const</span> <span class="keyword">class="type">int</span> funcCount, <span class="comment"> class=class="str">"cmt">// Number of functions to test</span> <span class="keyword">const</span> class="type">class="kw">color clrConv, <span class="comment"> class=class="str">"cmt">// Visualization class="type">class="kw">color</span> <span class="keyword">class="type">class="kw">double</span> &allScore, <span class="comment"> class=class="str">"cmt">// Total score of all tests</span> <span class="keyword">class="type">class="kw">double</span> &allTests) <span class="comment"> class=class="str">"cmt">// Total number of tests</span> { <span class="keyword">if</span> (funcCount <= <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span>; <span class="comment"> class=class="str">"cmt">// If the number of functions = class="num">0, exit the function</span> allTests++; <span class="comment"> class=class="str">"cmt">// Increase the total number of tests</span> <span class="keyword">if</span> (Video_P) <span class="comment"> class=class="str">"cmt">// If visualization is enabled</span> { ST.DrawFunctionGraph(f); <span class="comment"> class=class="str">"cmt">// Draw the function graph</span> ST.SendGraphToCanvas(); <span class="comment">class=class="str">"cmt">// Send the graph to the canvas</span> ST.MaxMinDr (f); <span class="comment"> class=class="str">"cmt">// Determine the maximum and minimum of the function</span> ST.Update (); <span class="comment">class=class="str">"cmt">// Update the visualization</span> } <span class="comment">class=class="str">"cmt">//----------------------------------------------------------------------------</span> C_AO_Utilities Ut; <span class="comment"> class=class="str">"cmt">// Utilities for handling numbers</span> <span class="keyword">class="type">int</span> xConv = <span class="number">class="num">0.0</span>; <span class="comment"> class=class="str">"cmt">// Variable for converting along the X axis</span> <span class="keyword">class="type">int</span> yConv = <span class="number">class="num">0.0</span>; <span class="comment"> class=class="str">"cmt">// Variable for converting along the Y axis</span> <span class="keyword">class="type">class="kw">double</span> aveResult = <span class="number">class="num">0.0</span>; <span class="comment"> class=class="str">"cmt">// Average test result</span> <span class="keyword">class="type">int</span> aveRunsFF = <span class="number">class="num">0</span>; <span class="comment"> class=class="str">"cmt">// Average number of function runs</span> <span class="keyword">class="type">int</span> <span class="keyword">params</span> = funcCount * <span class="number">class="num">2</span>; <span class="comment"> class=class="str">"cmt">// Number of parameters(class="num">2 for each function)</span> <span class="keyword">class="type">int</span> 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 量级看耗时差异。
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。外汇与贵金属参数寻优高风险,回测平均分高只代表该样本集上收敛效率倾向更优,实盘可能失效。
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 里直接挂脚本就能复跑三套基准函数,自己看收敛曲线。