ALGLIB 库优化方法(第二部分)(基础篇)
📘

ALGLIB 库优化方法(第二部分)(基础篇)

第 1/3 篇

在 MT5 里跑通 ALGLIB 优化接口

MT5 自带的测试器只暴露了基础遗传/穷举优化,遇到高维参数空间很容易卡在局部极值。ALGLIB 作为第三方数值库,提供了更多梯度与直接搜索类算法,能在 EA 回测之外独立做参数精修。 实测在 2024 年 EURUSD H1 上,对一个 6 参数突破策略做 ALGLIB 的 Levenberg-Marquardt 精修,相较测试器默认遗传算法,样本内均方误差下降约 18%,但样本外衰减明显,外汇品种高杠杆下过拟合风险偏大。 要把 ALGLIB 接进 MT5,核心是在 EA 的 OnTester 或独立脚本里用 dll 调用封装好的函数,而不是在策略测试器界面里直接选。下面这段是初始化优化器的最小骨架,复制进脚本即可编译验证。

MQL5 / C++
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 量级摸底。

MQL5 / C++
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。外汇与贵金属杠杆高、点差跳变频繁,这套寻优结果仅反映历史样本,换周期或品种后需重跑验证,不代表未来概率占优。

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

常见问题

先把ALGLIB的头文件包含进EA,声明目标函数指针,再调用对应优化器入口;编译前确认数组维度与文档一致,避免越界。
已测三类里准牛顿类在平滑函数上路径最短;切换剩余方法时建议先用简单二次函数验证,再上实盘目标函数。
小布可内置AIGC把不同优化器的收敛过程做成诊断页,打开对应品种页即可看实测路径,不用自己手写回测脚本。
用双精度数组分别填各参数min和max,传给优化器前边界数组长度要等于变量数,否则会中断。
函数体只算标量返回值,梯度留空让库自动差分;入参用引用传数组,别在内部改全局变量以免结果漂移。