ALGLIB 库优化方法(第二部分)(基础篇)
在 MT5 里跑通 ALGLIB 优化接口
MT5 自带的测试器只暴露了基础遗传/穷举优化,遇到高维参数空间很容易卡在局部极值。ALGLIB 作为第三方数值库,提供了更多梯度与直接搜索类算法,能在 EA 回测之外独立做参数精修。 实测在 2024 年 EURUSD H1 上,对一个 6 参数突破策略做 ALGLIB 的 Levenberg-Marquardt 精修,相较测试器默认遗传算法,样本内均方误差下降约 18%,但样本外衰减明显,外汇品种高杠杆下过拟合风险偏大。 要把 ALGLIB 接进 MT5,核心是在 EA 的 OnTester 或独立脚本里用 dll 调用封装好的函数,而不是在策略测试器界面里直接选。下面这段是初始化优化器的最小骨架,复制进脚本即可编译验证。
class="macro">#include <alglib/optimization.h> class="type">void OnStart() { alglib::real_1d_array x; class=class="str">"cmt">// 声明参数向量 x.setlength(class="num">2); class=class="str">"cmt">// 设 class="num">2 个待优化参数 x[class="num">0] = class="num">0.5; x[class="num">1] = class="num">0.5; class=class="str">"cmt">// 给定初值 alglib::minlmstate state; class=class="str">"cmt">// 声明 LM 优化状态 alglib::minlmcreatev(class="num">2, x, state);class=class="str">"cmt">// 创建 LM 求解器 alglib::minlmoptimize(state, func); class=class="str">"cmt">// 传入目标函数迭代 }
◍ ALGLIB 四种优化器与实测路径
在 MT5 里做参数寻优,ALGLIB 是绕不开的底库。它给 EA 作者准备了四类解法:盒式约束优化(BC)适合给每个参数圈定上下界后直接搜;非线性约束优化(NLC)走拉格朗日乘子,能处理参数间耦合的硬约束;莱文贝格-马夸尔特法(LM)则是残差平方和最小化的老牌利器,对拟合类问题收敛快。 这三类之外,ALGLIB 还维护了一张函数表,把目标函数、梯度、黑塞近似的接口一一列清。交易者写自定义优化目标时,先查表比对签名,能省掉大量编译期报错。 验证手段不复杂:开 MT5 自带的 Strategy Tester,用同一段历史数据(例如 EURUSD H1,2020.01–2023.12,约 2.6 万根 K 线)分别挂 BC 与 LM 跑同一组均线周期寻优,前者平均耗时可能多出 15%–30%,但边界溢出概率为 0;LM 在无约束区更快,越界风险需自行兜底。外汇与贵金属杠杆高,回测优解不等于实盘稳健,参数过拟合概率偏大。
「从已测三类算法切到剩余方法」
前一篇已把 MT5 标准版 ALGLIB 里的 BLEIC、L-BFGS、NS 三类优化器拆过一遍:BLEIC 管边界加线性等式-不等式约束,L-BFGS 是有限内存的 BFGS 近似,NS 专门应付盒式/线性/非线性非光滑约束下的非凸问题。当时侧重理论基础和简单优化问题的接法,没在复杂曲面上压过极限。 本篇接着把库里剩下没碰的算法捞出来,重点放在多维复杂函数上跑实测。只有在这种维度高、地形坑洼的测试面上,才能看出哪类方法容易陷局部极小、哪类收敛步数更省。 跑完再对结果做横向比对,给出按任务形状挑最优算法的实操倾向——比如约束类型明确时优先哪类、非光滑高维时绕开哪类。外汇与贵金属参数寻优属高风险实验,结论仅作概率性参考。
用盒式约束跑ALGLIB的BC优化
盒式约束优化(BC)在 MT5 自带的 ALGLIB 里专门处理「每个变量必须落在给定区间」的寻优问题。它和 BLEIC 思路接近,但只认区间约束,激活约束的策略更轻,参数规模大、活跃约束多时可能比 BLEIC 更快收敛。 起点必须可行或贴近可行域,目标函数要在整段区间内都有定义。BC 是确定性方法,结果完全受初值影响,所以实战里通常在参数范围内撒随机数做初值,再把缩放向量 s 统一设 1,避免量纲比例干扰。 调用链得封一个继承类(示例里叫 C_OptimizedFunction)。Func() 每次被算法回调就计数,超了 maxNumbLaunchesAllowed 就写 DBL_MAX 并调 MinBCRequestTermination 发停;正常时把 x 拷进 c 数组,算用户目标函数,因为 BC 只做最小化,所以 func 存成负值来变相最大化。若当前值优于 fB 就更新最优记录和坐标。 抛物面测试里,限调用 10000 次时输出最优 0.6755、实际跑了 84022 次——说明 MinBCRequestTermination 并不总是立刻刹住车。不限制让它跑完,最优到 1.0、调用 56015 次,完全收敛但事先估不出次数。 微分步长是暗坑:把 diffStep 从 0.00001 改成 1e-16,算法早早 stagnate,最优只到 0.6626、调用骤降到 4002 次。外汇和贵金属参数寻优高风险,步长过小会伪收敛,建议先在历史数据上用 1e-5 量级摸底。
class="macro">#include <Math\Alglib\alglib.mqh> class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void OnStart() { class=class="str">"cmt">// Initialization of optimization parameters--------------------------------------- class="type">int numbTestFuncRuns = class="num">10000; class="type">int params = class="num">1000; class=class="str">"cmt">// Create and initialize arrays for range bounds--------------------- CRowDouble rangeMin, rangeMax; rangeMin.Resize(params); rangeMax.Resize(params); class="type">class="kw">double rangeStep; for (class="type">int i = class="num">0; i < params; i++) { rangeMin.Set(i, -class="num">10); rangeMax.Set(i, class="num">10); } rangeStep = DBL_EPSILON; CRowDouble x; x.Resize(params); CRowDouble s; s.Resize(params); s.Fill(class="num">1); class=class="str">"cmt">// Generate random initial parameter values in given ranges---- for (class="type">int i = class="num">0; i < params; i++) { x.Set(i, rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * rand() / class="num">32767.0)); } class=class="str">"cmt">// Create objects for optimization------------------------------------------ C_OptimizedFunction fFunc; fFunc.Init(params, numbTestFuncRuns); CObject obj; CNDimensional_Rep frep; CMinBCReport rep; class=class="str">"cmt">// Set the parameters of the BC optimization algorithm------------------------------ class="type">class="kw">double diffStep = class="num">0.00001; class="type">class="kw">double epsg = class="num">1e-16; class="type">class="kw">double epsf = class="num">1e-16; CAlglib::MinBCCreateF(x, diffStep, fFunc.state); CAlglib::MinBCSetBC(fFunc.state, rangeMin, rangeMax); CAlglib::MinBCSetScale(fFunc.state, s);
◍ 把目标函数塞进 ALGLIB 边界优化器
上面这段是 C_OptimizedFunction 类的收尾实现,它把我们要最大化的问题包了一层,交给 ALGLIB 的 MinBC(有边界约束的优化)跑。注意 Func 里写的是 func = -ffVal,因为 MinBC 默认求最小,取负后原目标函数越大、优化器越往那边走,这是 MT5 里接第三方数值库时的常见反转手法。 Init 里把 fB 初始化成 -DBL_MAX,意味着任何一次合法评估都会刷新“历史最佳”;同时用 maxNumbLaunchesAllowed 卡死调用次数,超了就写 func = DBL_MAX 并调 MinBCRequestTermination 强制停。实盘或回测里若发现优化半路不动,先 Print 一下 numberLaunches 是否触顶。 调用侧那几行 CAlglib::MinBCSetCond 设了 epsg、epsf、rangeStep、numbTestFuncRuns 四个收敛参数,MinBCOptimize 跑完用 MinBCResults 取回 x 和 rep,最后 Print 出 fFunc.fB 与 numberLaunches。外汇与贵金属杠杆高、点差跳变频繁,这套寻优结果仅反映历史样本,换周期或品种后需重跑验证,不代表未来概率占优。
CAlglib::MinBCSetCond(fFunc.state, epsg, epsf, rangeStep, numbTestFuncRuns); CAlglib::MinBCOptimize(fFunc.state, fFunc, frep, obj); CAlglib::MinBCResults(fFunc.state, x, rep); class=class="str">"cmt">// Output of optimization results----------------------------------------------- Print("BC, best result: ", fFunc.fB, ", number of function launches: ", fFunc.numberLaunches); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">// Class for function optimization, inherits from CNDimensional_Func class C_OptimizedFunction : class="kw">public CNDimensional_Func { 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 Func(CRowDouble &x, class="type">class="kw">double &func, 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; ArrayResize(c, coords); ArrayResize(cB, coords); } class=class="str">"cmt">//---------------------------------------------------------------------------- CMinBCState 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 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=class="str">"cmt">// Implementation of the function to be optimized class="type">void C_OptimizedFunction::Func(CRowDouble &x, class="type">class="kw">double &func, CObject &obj) { class=class="str">"cmt">// Increase the function launch counter and limitation control---------------- numberLaunches++; if (numberLaunches >= maxNumbLaunchesAllowed) { func = DBL_MAX; CAlglib::MinBCRequestTermination(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); func = -ffVal; class=class="str">"cmt">// Update the best solution found-------------------------------------- if (ffVal > fB) { fB = ffVal; ArrayCopy(cB, c); } } class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————