ALGLIB库优化方法(第一部分)·进阶篇
(2/3)· 边界约束与有限内存拟牛顿法如何落地交易系统参数寻优
「用 ALGLIB 的 BLEIC 跑多维参数寻优」
在 MT5 里做参数寻优,ALGLIB 的 MinBLEIC 求解器比平台自带优化器更可控,尤其适合带边界约束的非线性目标。下面这段是直接可用的调用骨架,核心是把状态对象、边界与收敛阈值分开设置。 epsf 取 1e-16 控制函数值收敛精度,epsi 取 0.00001 作为外层相对容差;这两个值若调大,迭代次数会明显下降,但可能错过外汇 EA 回测里的尖锐收益峰。 回调函数里用 numberLaunches 计数并在超限时写 func = DBL_MAX 主动终止,能避免贵金属策略在窄区间反复求值拖死终端。开 MT5 把下面代码贴进 EA 头文件,改 ObjectiveFunction 即可验证你自己的目标。
class="type">class="kw">double epsf = class="num">1e-16; class="type">class="kw">double epsi = class="num">0.00001; CAlglib::MinBLEICCreateF(x, diffStep, fFunc.state); CAlglib::MinBLEICSetBC(fFunc.state, rangeMin, rangeMax); CAlglib::MinBLEICSetInnerCond(fFunc.state, epsg, epsf, rangeStep); CAlglib::MinBLEICSetOuterCond(fFunc.state, rangeStep, epsi); CAlglib::MinBLEICOptimize(fFunc.state, fFunc, frep, class="num">0, obj); CAlglib::MinBLEICResults(fFunc.state, x, rep); class=class="str">"cmt">// Output of optimization results----------------------------------------------- Print("BLEIC, 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: 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">//---------------------------------------------------------------------------- CMinBLEICStateShell 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::MinBLEICRequestTermination(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;
◍ 把当前解写进最优缓存
这段逻辑只干一件事:当新算出的适应度 ffVal 大于已记录的最优值 fB 时,就刷新最优解。它出现在群体迭代的内层,每评估完一个候选解都会走一遍,属于遗传算法里“精英保留”的最小实现。 注意比较方向是大于号,说明该问题把适应度定义成越大越好(比如收益类指标)。若你的目标函数越小越好,这里必须改成 ffVal < fB,否则最优缓存永远停在初始值。外汇与贵金属市场波动剧烈,这类参数寻优结果仅代表历史样本表现,实盘存在显著回撤风险。 下面拆一下这三行核心代码:先判断 ffVal > fB 进入分支;随后把 fB 赋值为当前更高的 ffVal;最后用 ArrayCopy 把候选向量 c 整体拷进最优缓存 cB,保证下一轮迭代能基于真实最优继续搜索。
class=class="str">"cmt">// Update the best solution found-------------------------------------- if (ffVal > fB) { fB = ffVal; ArrayCopy(cB, c); } } class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
L-BFGS 的内存账与抛物面实测坑
L-BFGS 是拟牛顿法里专啃千维以上大规模优化的变种,它不存完整 Hessian,只保留最近 M(通常 3–10)对位置与梯度来近似曲率,单次迭代成本压到 O(N·M)。近似矩阵强制正定,所以搜索方向永远朝下,不会因函数弯曲而跑偏。 状态由 CMinLBFGSStateShell 接管,外部可用 MinLBFGSRequestTermination 强制叫停。它和 BLEIC 不同:能直接限目标函数运行次数,但没法给待优化参数设边界。 跑抛物面测试时,设 M=1、限运行 10000 次,实际却调了 24006 次才出最优值 0.6743844728276278——限制形同虚设,超了两倍多。放开次数限制后,完全收敛到 1.0,但函数调用飙到 52013 次,次数失控。 微分步长也是雷区。把 diffStep 压到 1e-16,算法 4001 次就过早停住,卡在 0.6746423814003036。外汇与贵金属模型调参用这法子,高杠杆下错一步方向可能放大回撤,务必先在 MT5 脚本里复跑确认。 别把次数限制当保险 代码里 numbTestFuncRuns 传进 MinLBFGSSetCond,实测运行次数会翻倍突破,真要控成本得在循环里自己计数并调 RequestTermination。
<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">// Create and initialize arrays for search range bounds--------------</span> <span class="keyword">class="type">class="kw">double</span> rangeMin [], rangeMax [], rangeStep; <span class="functions">ArrayResize</span> (rangeMin, params); <span class="functions">ArrayResize</span> (rangeMax, params); <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < params; i++) { rangeMin [i] = -<span class="number">class="num">10</span>; rangeMax [i] = <span class="number">class="num">10</span>; } rangeStep = <span class="macro">DBL_EPSILON</span>; <span class="keyword">class="type">class="kw">double</span> x []; <span class="keyword">class="type">class="kw">double</span> s []; <span class="functions">ArrayResize</span> (x, params); <span class="functions">ArrayResize</span> (s, params); <span class="functions">ArrayInitialize</span> (s, <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 style="class="type">color:rgb(class="num">153, class="num">153, class="num">153);">-</span></span><span style="class="type">color:rgb(class="num">153, class="num">153, class="num">153);">-</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 [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 style="class="type">color:rgb(class="num">153, class="num">153, class="num">153);">---</span></span><span style="class="type">color:rgb(class="num">153, class="num">153, class="num">153);">-</span> C_OptimizedFunction fFunc; fFunc.Init(params, numbTestFuncRuns); CObject obj; CNDimensional_Rep frep; CMinLBFGSReportShell 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 L-BFGS 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> epsg = <span class="number">class="num">1</span>e-<span class="number">class="num">16</span>; <span class="keyword">class="type">class="kw">double</span> epsf = <span class="number">class="num">1</span>e-<span class="number">class="num">16</span>; CAlglib::MinLBFGSCreateF (<span class="number" style="background-class="type">color:rgb(class="num">255, class="num">235, class="num">85);">class="num">1</span>, x, diffStep, fFunc.state); CAlglib::MinLBFGSSetCond (fFunc.state, epsg, epsf, rangeStep, <span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);">numbTestFuncRuns</span>); CAlglib::MinLBFGSSetScale(fFunc.state, s); CAlglib::MinLBFGSOptimize(fFunc.state, fFunc, frep, <span class="number">class="num">0</span>, obj); CAlglib::MinLBFGSResults (fFunc.state, x, rep); <span class="comment">class=class="str">"cmt">//----------------------------------------------------------------------------</span> <span class="functions">Print</span> (<span class="class="type">class="kw">string">"L-BFGS, 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">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">// Class for function optimization, inherits from CNDimensional_Func</span> <span class="keyword">class</span> C_OptimizedFunction : <span class="keyword">class="kw">public</span> CNDimensional_Func { <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--------------------------------------------------------------------</span> C_OptimizedFunction(<span class="keyword">class="type">void</span>) { }
「把目标函数塞进LBFGS优化壳里」
上面这段是 C_OptimizedFunction 类的收尾与 Func 实现,核心是把用户自定义的目标函数包进 ALGLIB 的 LBFGS 状态机里跑。Init 里先把调用计数器 numberLaunches 归零,fB 置为 -DBL_MAX(约 -1.797e308),再按 coords 维度给 c 和 cB 两个数组Resize,这一步维度填错会直接数组越界。 Func 每次被优化器拉起时先 numberLaunches++,一旦超过 maxNumberLaunchesAllowed 就给 func 填 DBL_MAX 并调 MinLBFGSRequestTermination 强制停机——这意味着你设的允许调用次数直接卡死了搜索预算,外汇参数寻优时设太小容易陷在局部极值。 坐标从 x 拷进内部数组 c 后算 ObjectiveFunction(c),注意这里 func = -ffVal,说明类默认走最大化;若你的目标本就是最小化,符号别漏改。ffVal 优于 fB 时刷新最佳值与坐标到 cB,跑完直接读 cB 就是当前最优参数组合,贵金属网格参数粗筛可先拿这套验证。 高风险提示:MT5 策略优化结果仅反映历史样本,实盘外汇/贵金属波动可能使参数失效,请控制仓位。
~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">//---------------------------------------------------------------------------- CMinLBFGSStateShell 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::MinLBFGSRequestTermination(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); } }