ALGLIB库优化方法(第一部分)·进阶篇
📉

ALGLIB库优化方法(第一部分)·进阶篇

(2/3)· 边界约束与有限内存拟牛顿法如何落地交易系统参数寻优

含代码示例偏理论 第 2/3 篇
很多交易者把 ALGLIB 的优化器当黑箱调用,结果在带约束的变量空间里跑出越界参数还以为是样本巧合。方法名杂乱、访问顺序不统一,导致进阶优化常常卡在第一步。这篇先把边界与线性约束类方法拆开,少走弯路。

「用 ALGLIB 的 BLEIC 跑多维参数寻优」

在 MT5 里做参数寻优,ALGLIB 的 MinBLEIC 求解器比平台自带优化器更可控,尤其适合带边界约束的非线性目标。下面这段是直接可用的调用骨架,核心是把状态对象、边界与收敛阈值分开设置。 epsf 取 1e-16 控制函数值收敛精度,epsi 取 0.00001 作为外层相对容差;这两个值若调大,迭代次数会明显下降,但可能错过外汇 EA 回测里的尖锐收益峰。 回调函数里用 numberLaunches 计数并在超限时写 func = DBL_MAX 主动终止,能避免贵金属策略在窄区间反复求值拖死终端。开 MT5 把下面代码贴进 EA 头文件,改 ObjectiveFunction 即可验证你自己的目标。

MQL5 / C++
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,保证下一轮迭代能基于真实最优继续搜索。

MQL5 / C++
  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。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span> ()
{
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Initialization of optimization parameters---------------------------------------</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span> numbTestFuncRuns = <span class="number">class="num">10000</span>;
&nbsp;&nbsp;<span class="keyword">class="type">int</span> params&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <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>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> rangeMin [], rangeMax [], rangeStep;
&nbsp;&nbsp;<span class="functions">ArrayResize</span> (rangeMin,&nbsp;&nbsp;params);
&nbsp;&nbsp;<span class="functions">ArrayResize</span> (rangeMax,&nbsp;&nbsp;params);
&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; params; i++)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;rangeMin&nbsp;&nbsp;[i] = -<span class="number">class="num">10</span>;
&nbsp;&nbsp;&nbsp;&nbsp;rangeMax&nbsp;&nbsp;[i] =&nbsp;&nbsp;<span class="number">class="num">10</span>;
&nbsp;&nbsp;}
&nbsp;&nbsp;rangeStep =&nbsp;&nbsp;<span class="macro">DBL_EPSILON</span>;
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> x [];
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> s [];
&nbsp;&nbsp;<span class="functions">ArrayResize</span> (x, params);
&nbsp;&nbsp;<span class="functions">ArrayResize</span> (s, params);
&nbsp;&nbsp;<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>
&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; params; i++)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;x [i] = rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * <span class="functions">rand</span> () / <span class="number">class="num">32767.0</span>);
&nbsp;&nbsp;}
&nbsp;&nbsp;<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>
&nbsp;&nbsp;C_OptimizedFunction&nbsp;&nbsp;fFunc; fFunc.Init(params, numbTestFuncRuns);
&nbsp;&nbsp;CObject&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;obj;
&nbsp;&nbsp;CNDimensional_Rep&nbsp;&nbsp;&nbsp;&nbsp;frep;
&nbsp;&nbsp;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>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> diffStep = <span class="number">class="num">0.00001</span>;
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> epsg&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">1</span>e-<span class="number">class="num">16</span>;
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> epsf&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">1</span>e-<span class="number">class="num">16</span>;
&nbsp;&nbsp;CAlglib::MinLBFGSCreateF&nbsp;&nbsp;(<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);
&nbsp;&nbsp;CAlglib::MinLBFGSSetCond&nbsp;&nbsp;(fFunc.state, epsg, epsf, rangeStep, <span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);">numbTestFuncRuns</span>);
&nbsp;&nbsp;CAlglib::MinLBFGSSetScale(fFunc.state, s);
&nbsp;&nbsp;CAlglib::MinLBFGSOptimize(fFunc.state, fFunc, frep, <span class="number">class="num">0</span>, obj);
&nbsp;&nbsp;CAlglib::MinLBFGSResults&nbsp;&nbsp;(fFunc.state, x, rep);
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//----------------------------------------------------------------------------</span>
&nbsp;&nbsp;<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
{
&nbsp;&nbsp;<span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--------------------------------------------------------------------</span>
&nbsp;&nbsp;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 策略优化结果仅反映历史样本,实盘外汇/贵金属波动可能使参数失效,请控制仓位。

MQL5 / C++
  ~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);
  }
}
把约束诊断交给小布
这些带边界与线性约束的优化诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到变量越界提示,你只需专注目标函数设计。

常见问题

BLEIC 显式支持边界与线性等式/不等式约束,L-BFGS 主要处理无约束或仅箱型边界的有限内存拟牛顿寻优,前者更适合规则明确的参数空间。
价格序列本身是离散点,解析梯度难以直接获得,数值法由优化器内部扰动变量估算梯度,省去手动推导且贴合实际回测场景。
可以,小布盯盘内置的 AIGC 看板能标注参数越界与约束冲突,并给出历史适应度分布,减少人工核对代码输出的时间。
有可能,单以某段历史利润最大化容易陷入曲线拟合,建议引入样本外或鲁棒性约束来降低过拟合概率。