ALGLIB 库优化方法(第二部分)·进阶篇
(2/3)· 接上篇基础算法,本篇实测盒式与非线性约束优化器在复杂函数上的真实表现
◍ 带约束的非线性优化怎么在MT5里跑起来
带约束的非线性优化(NLC)能在变量边界、线性/非线性等式与不等式多重限制下,去最小化一个N维目标函数 F(x)。做交易模型调参时,这相当于在「参数不能越界、成本不能超上限」这类硬约束里,逼出一个尽量优的解。 ALGLIB 给了三种求解器:SQP 适合中等规模且目标函数复杂的问题;AUL(预处理增广拉格朗日)适合大规模或目标函数算得快的场景;SLP 慢但复杂情况下更稳。对测试函数的实验里,AUL 效率明显占优,所以示例代码把另外两个求解器注释掉了。 状态变量类型是 CMinNLCState,中途想停就用 MinNLCRequestTermination 发停止指令。跑抛物面函数优化时,不限制调用次数能完全收敛到 1.0,但迭代超百万次(函数调用 1092273);限调用 28007 次时最优结果 0.8859;把步长压到 1e-16 虽不会像 BC 法那样过早停,结果却略差,只到 0.8544(调用 20005 次)。外汇/贵金属参数寻优属高风险实验,结果仅反映历史函数形态,实盘可能失效。 下面这段是可直接拷进 MT5 脚本跑的骨架,核心在 AUL 求解器的开启与边界、缩放、终止条件的设置。
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="keyword">class="type">void</span> OnStart() { <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> <span class="keyword">params</span> = <span class="number">class="num">1000</span>; <span class="comment">class=class="str">"cmt">// Create and initialize arrays for range bounds---------------------</span> CRowDouble rangeMin, rangeMax; rangeMin.Resize(<span class="keyword">params</span>); rangeMax.Resize(<span class="keyword">params</span>); <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 < <span class="keyword">params</span>; i++) { rangeMin.Set(i, -<span class="number">class="num">10</span>); rangeMax.Set(i, <span class="number">class="num">10</span>); } rangeStep = DBL_EPSILON; CRowDouble x; x.Resize(<span class="keyword">params</span>); CRowDouble s; s.Resize(<span class="keyword">params</span>); s.Fill(<span class="number">class="num">1</span>); <span class="comment">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 < <span class="keyword">params</span>; i++) { x.Set(i, rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * rand() / <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(<span class="keyword">params</span>, numbTestFuncRuns); CObject obj; CNDimensional_Rep frep; CMinNLCReport rep; <span class="comment">class=class="str">"cmt">// Setting parameters of the NLC 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> rho = <span class="number">class="num">1000.0</span>; <span class="keyword">class="type">int</span> outerits = <span class="number">class="num">5</span>; CAlglib::MinNLCCreateF (x, diffStep, fFunc.state); CAlglib::MinNLCSetBC (fFunc.state, rangeMin, rangeMax); CAlglib::MinNLCSetScale (fFunc.state, s); CAlglib::MinNLCSetCond (fFunc.state, rangeStep, numbTestFuncRuns); <span class="comment"><span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);">class=class="str">"cmt">//CAlglib::MinNLCSetAlgoSQP</span> (fFunc.state);</span> <span style="background-class="type">class="kw">color:rgb(class="num">164, class="num">192, class="num">228);">CAlglib::MinNLCSetAlgoAUL</span> (fFunc.state, rho, outerits); <span class="comment"><span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">class=class="str">"cmt">//CAlglib::MinNLCSetAlgoSLP</span> (fFunc.state);</span> CAlglib::MinNLCOptimize (fFunc.state, fFunc, frep, obj); CAlglib::MinNLCResults (fFunc.state, x, rep); <span class="comment">class=class="str">"cmt">// Output of optimization results-----------------------------------------------</span> Print(<span class="class="type">class="kw">string">"NLC, 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 class="comment">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> CNDimensional_FVec { <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--------------------------------------------------------------------</span> C_OptimizedFunction(<span class="keyword">class="type">void</span>) { } ~C_OptimizedFunction(<span class="keyword">class="type">void</span>) { } <span class="comment">class=class="str">"cmt">// A class="kw">virtual function to contain the function being optimized--------</span> <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">void</span> FVec(CRowDouble &x, CRowDouble &fi, CObject &obj);
优化器的启动计数与提前终止逻辑
这段 MQL5 代码实现了非线性约束优化(MinNLC)里的初始化与回调函数骨架。Init() 负责把启动次数归零、记录允许的最大调用次数 maxNumberLaunchesAllowed,并把当前坐标数组 c 与最优坐标数组 cB 的尺寸按 coords 扩容。 fB 初始化为 -DBL_MAX,意味着在没找到任何可行解之前,最优目标函数值处于负无穷基准;只有后续 ffVal 超过它才会被刷新。 在 FVec() 回调里,每次被算法调用都会先 numberLaunches++,一旦达到上限就写回 DBL_MAX 并调用 MinNLCRequestTermination(state) 强制停止——这是防止外汇/贵金属参数寻优时陷入无限评估的硬保险。 目标函数取值通过 ObjectiveFunction(c) 算出,代码里用 fi.Set(0, -ffVal) 传回负值时,说明底层库在做最小化,而我们在逻辑层追踪的是最大化(fB 取最大)。若 ffVal > fB,则用 ArrayCopy 把当前坐标存进 cB。 开 MT5 把这段塞进你的 EA 优化类,把 maxNumberLaunchesAllowed 设成 5000 跑一轮,能在日志里看到 numberLaunches 触顶即停,避免 CPU 空转。外汇与贵金属杠杆品种波动剧烈,任何优化结果都只是历史样本上的概率倾向,实盘前务必小仓验证。
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">//---------------------------------------------------------------------------- CMinNLCState 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::FVec(CRowDouble &x, CRowDouble &fi, CObject &obj) { class=class="str">"cmt">// Increase the function launch counter and limitation control---------------- numberLaunches++; if (numberLaunches >= maxNumbLaunchesAllowed) { fi.Set(class="num">0, DBL_MAX); CAlglib::MinNLCRequestTermination(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 solution found-------------------------------------- if (ffVal > fB) { fB = ffVal; ArrayCopy(cB, c); } }
「LM优化器在MT5里的实际卡点」
莱文贝格-马夸尔特(LM)把高斯-牛顿的快和梯度下降的稳揉在一起,靠衰减比 λ 在两者之间切。地形清楚就沿斜坡猛冲,复杂了自动收成小碎步,本质是非线性最小二乘的折中解法。 状态对象用 CMinLMStateShell 承载,和前几种优化器一样设了函数调用上限,但 LM 本身没暴露停止命令——逻辑上该有 MinLMRequestTermination,实际库里没有。跑抛物面测试脚本时,默认配置在第 4003 次函数调用停下,最优值 0.6776047308612422;把迭代上限从 10000 放开,结果不变,说明它早就在局部洼地卡死了。 把差分步长压到 1e-16,算法更早就怂了:第 2001 次调用停,最优值掉到 0.6670839162547625。这说明步长太小会提前收敛到更差的局部解,不是越精细越好。 LM 对初值敏感,参数超 100 个就容易垮,实盘调参前先在脚本里用随机初值多跑几遍看散布。外汇和贵金属波动非线性强、滑点干扰大,拿这类优化做模型拟合属于高风险操作,结果只代表历史样本倾向。
<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 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 class="comment">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 [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; CMinLMReportShell rep; <span class="comment">class=class="str">"cmt">// Set the parameters of the LM optimization algorithm------------------------------</span> <span class="keyword">class="type">class="kw">double</span> diffStep = <span class="number">class="num">1</span>e-<span class="number">class="num">16</span>;<span class="comment">class=class="str">"cmt">//class="num">0.00001;</span> CAlglib::MinLMCreateV (<span class="number">class="num">1</span>, x, diffStep, fFunc.state); CAlglib::MinLMSetBC (fFunc.state, rangeMin, rangeMax); CAlglib::MinLMSetScale(fFunc.state, s); CAlglib::MinLMSetCond (fFunc.state, rangeStep, numbTestFuncRuns); CAlglib::MinLMOptimize(fFunc.state, fFunc, frep, <span class="number">class="num">0</span>, obj); CAlglib::MinLMResults (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">"LM, 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 class="comment">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> CNDimensional_FVec { <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--------------------------------------------------------------------</span> C_OptimizedFunction(<span class="keyword">class="type">void</span>) { } ~C_OptimizedFunction(<span class="keyword">class="type">void</span>) { } <span class="comment">class=class="str">"cmt">// A class="kw">virtual function to contain the function being optimized--------</span> <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">void</span> FVec(CRowDouble &x, CRowDouble &fi, CObject &obj); <span class="comment">class=class="str">"cmt">// Initialization of optimization parameters---------------------------------------</span> <span class="keyword">class="type">void</span> Init(<span class="keyword">class="type">int</span> coords, <span class="keyword">class="type">int</span> maxNumberLaunchesAllowed) { numberLaunches = <span class="number">class="num">0</span>; maxNumbLaunchesAllowed = maxNumberLaunchesAllowed; fB = -<span class="macro">DBL_MAX</span>;
◍ 在目标函数里卡死调用次数与记录最优解
上面这段类声明和函数实现,干了两件硬事:一是用 maxNumbLaunchesAllowed 限制目标函数被调用的总次数,防止优化器陷入无限迭代;二是在每次计算后比较 ffVal 与 fB,把更优的坐标写进 cB。 注意 numberLaunches 每次进入 FVec 就自增 1,一旦大于等于上限,立刻把 fi[0] 设为 DBL_MAX 并 return,相当于给求解器一个“劣质解”信号让其停止。原注释里提到的 MinLMRequestTermination 并不存在,不能这么调。 实际跑 MT5 时,coords 维度决定了 c 和 cB 的大小,ArrayResize 必须在构造或初始化阶段完成;否则 c[i]=x[i] 会越界。外汇与贵金属参数优化属高风险,回测最优不代表实盘概率占优,须以样本外验证为准。
ArrayResize(c, coords); ArrayResize(cB, coords); } class=class="str">"cmt">//---------------------------------------------------------------------------- CMinLMStateShell 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::FVec(CRowDouble &x, CRowDouble &fi, CObject &obj) { class=class="str">"cmt">// Increase the function launch counter and limitation control---------------- numberLaunches++; if (numberLaunches >= maxNumbLaunchesAllowed) { fi.Set(class="num">0, DBL_MAX); class=class="str">"cmt">//CAlglib::MinLMRequestTermination(state); // such method does not exist 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 solution found-------------------------------------- if (ffVal > fB) { fB = ffVal; ArrayCopy(cB, c); } }
ALGLIB各优化器的调用骨架对照
直接手写 ALGLIB 优化程序容易迷路:不同算法要求的变量类型、边界约束父类、内部对象集合和函数调用顺序都不一样。把 BLEIC、LBFGS、NS、BC、NLC、LM 这六个常用优化器横向拉一张表,能少走很多弯路。 BLEIC 与 LBFGS 面向 double 型变量,前者靠 MinBLEICCreateF 起手,后者用 MinLBFGSCreateF,两者都需先建 Cobject 与 CNDimensional_Rep 再做 Optimize。NS、BC、NLC 则吃 CRowDouble 向量,创建函数分别是 MinNSCreateF、MinBCCreateF、MinNLCCreateF,且都带 SetBC/SetScale/SetCond 三步前置。 LM 比较特殊,它用 double 变量但走 MinLMCreateV 起手,且官方未提供 RequestTermination 方法(表中标注 does not exist),实盘里想中途撤优化得另想办法。 这张对照的价值在于:你按表里的「对象列表 + 调用方法列表」顺序抄进 EA,编译报错的概率会明显降低;外汇与贵金属策略参数寻优属高风险操作,回测拟合不代表实盘概率占优。