English Русский Español Português
preview
ALGLIB库优化方法(第一部分)

ALGLIB库优化方法(第一部分)

MetaTrader 5测试者 | 16 六月 2025, 14:00
50 1
Andrey Dik
Andrey Dik

内容

  1. 概述
  2. ALGLIB库的优化方法:


概述

MetaTrader 5终端的标准安装中包含了ALGLIB库,这是一个强大的数值分析工具,对交易系统开发者来说非常有用。ALGLIB库为用户提供了一系列适用范围广的数值分析方法,包括:

  • 线性代数——求解线性方程组、计算特征值和特征向量、矩阵分解。
  • 优化——一维和多维优化方法。
  • 插值与逼近——多项式插值和样条插值、使用最小二乘法进行函数逼近。
  • 数值积分与微分——积分方法(梯形法、辛普森法等)、数值微分。
  • 求解微分方程的数值方法——常微分方程及其数值解法。
  • 统计方法——参数估计、回归分析、随机数生成。
  • 傅里叶分析:快速傅里叶变换。

ALGLIB中提供的确定性优化方法基于梯度下降的各种变量,并允许使用解析法和数值法。在本文中,我们将重点关注数值方法,因为它们最适合交易者的实际操作。

值得注意的是,金融领域的许多问题本质上是离散的,因为价格数据以离散点的形式表示。因此,我们对使用梯度数值表示的方法特别感兴趣。用户无需计算梯度——这一任务由优化方法承担。

由于方法名称和访问顺序缺乏统一性,精通ALGLIB优化方法通常具有挑战性。本文的主要目标是填补有关该库的信息空白,并提供简单的使用示例。让我们定义一些基本术语和概念,以便更好地理解这些方法的用途。

优化是在给定条件和约束下寻找最优解决方案的过程。搜索意味着至少存在两种解决问题的选项,必须从中选择最优的一个。

优化问题是一个难题,其中需要从满足特定条件的一组可能选项中找到最佳解决方案(最大值或最小值)。优化问题的主要要素:

  1. 目标函数(适应度函数)。这是需要最大化或最小化的函数。例如,最大化利润或最小化成本(利润或成本是优化标准)。
  2. 变量。这些是可以更改的参数,以实现最优结果。
  3. 约束条件。这些是在寻找最优解时必须满足的条件。

目标函数可以是用户指定的任何函数,它接受输入(待优化)并产生一个用作优化标准的值。例如,目标函数可能是在历史数据上测试交易系统,其中函数参数是交易系统的设置,输出值是其运行所需的质量指标。

约束条件的类型:

  1. 边界约束(箱约束)——对变量值施加的限制,例如,变量“x”只能在1到3的范围内。
  2. 线性等式约束——变量表达式必须等于某个数的条件。例如,(x + y = 5) 是一个线性等式。
  3. 线性不等式约束——变量表达式必须小于或等于(或大于或等于)某个数的条件。例如,(x - y >= 1)。

在下面的示例中,我们将仅考虑边界约束。因此,本文将通过简单的示例,揭示使用ALGLIB优化方法的有效技巧和简便方法。

我们将使用一个简单的需要最大化的目标函数作为优化问题的示例。它是一个具有单个最大值的平滑单调函数——一个倒置的抛物面。无论参数数量如何(参数属于[-10; 10]范围),该函数的值都在[0; 1]范围内。

//——————————————————————————————————————————————————————————————————————————————
//Paraboloid, F(Xn) ∈ [0.0; 1.0], X ∈ [-10.0; 10.0], maximization
double ObjectiveFunction (double &x [])
{
  double sum = 0.0;

  for (int i = 0; i < ArraySize (x); i++)
  {
    if (x [i] < -10.0 || x [i] > 10.0) return 0.0;
    sum += (-x [i] * x [i] + 100.0) * 0.01;
  }
  
  return sum /= ArraySize (x);
}
//——————————————————————————————————————————————————————————————————————————————


BLEIC(边界、线性等式-不等式约束)

BLEIC(边界、线性等式-不等式约束)是一类用于解决带有等式和不等式约束的优化问题的方法的统称。该方法名称源于约束条件的分类,即在当前点将约束条件分为活跃(active)和不活跃(inactive)两类。该方法将带有等式和不等式约束的问题转化为一系列仅受等式约束的子问题。活跃的不等式被视为等式处理,而不活跃的不等式则暂时被忽略(尽管我们仍会持续监控它们)。通俗地说,当前点会在可行集内移动,时而“紧贴”边界,时而“脱离”边界。

1. 它用来做什么?它用于寻找某个函数的极小值或极大值(例如,寻找最高利润或最低成本),同时考虑各种约束条件:

  • 变量值的边界(例如,价格不能为负)
  • 等式约束(例如,投资组合中权重的总和应等于100%)
  • 不等式约束(例如,风险不得超过某个水平)

2. 它如何工作?它采用有限内存技术,这意味着它不会存储所有中间计算结果,而只存储最重要的部分。它逐步向解决方案移动:

  • 评估当前情况
  • 确定移动方向
  • 朝该方向迈出一步
  • 检查是否违反了约束条件
  • 如有必要,调整移动方向

3. 特征:

  • 函数应“平滑”(无剧烈跳跃)
  • 能够找到局部极小值而非全局极小值
  • 对初始近似值(起始点)敏感

一个简单的例子:想象您正在一块土地上寻找建造房屋的地方。有以下约束条件:房屋必须达到一定大小(等式约束),必须距离地界至少X米(不等式约束),并且必须位于地界内(边界条件)。您希望视野最佳(目标函数)。BLEIC将逐渐在土地上“移动”这个想象中的房屋,同时遵守所有约束条件,直到找到视野最佳的位置。有关此方法以及后续算法的更多详细信息,请访问库作者页面

要在ALGLIB库中使用BLEIC方法及其他方法,我们需要包含相应的文件(该库随MetaTrader 5终端一起提供,用户无需额外安装任何内容)。

#include <Math\Alglib\alglib.mqh>

让我们开发一个脚本——一个使用ALGLIB方法高效工作的示例。我将突出显示在使用ALGLIB方法时常用的主要步骤。相应的代码块也以适当的颜色突出显示。

1. 定义问题的边界条件,例如适应度函数(目标函数)的调用次数、待优化参数的范围及其步长。对于ALGLIB方法,需要为待优化的参数“x”指定初始值(这些方法是确定性的,结果完全取决于初始值,因此我们将在问题参数的范围内应用随机数生成),以及设置缩放比例“s”(这些方法对参数之间的相对缩放比例敏感,在此情况下,我们将缩放比例设置为“1”)。

2. 声明算法运行所需的必要对象。

3. 设置算法的外部参数(设置项)。

4. 通过将待优化参数的范围和步长以及算法的外部参数传递给方法,来初始化算法。

5. 执行优化。

6. 获取优化结果以供进一步使用。

请记住,用户无法干预优化过程或在任何时候停止它。该方法会独立执行所有操作,在其过程中调用适应度函数。算法可能会任意次数地调用适应度函数(对于BLEIC方法,无法指定此停止参数),用户只能通过尝试向该方法传递停止命令来控制最大允许的调用次数。

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  // Initialization of optimization parameters---------------------------------------
  int numbTestFuncRuns = 10000;
  int params           = 1000;

  // Create and initialize arrays for range bounds---------------------
  double rangeMin [], rangeMax [], rangeStep;
  ArrayResize (rangeMin,  params);
  ArrayResize (rangeMax,  params);

  for (int i = 0; i < params; i++)
  {
    rangeMin  [i] = -10;
    rangeMax  [i] =  10;
  }
  rangeStep =  DBL_EPSILON;

  double x [];
  double s [];
  ArrayResize (x, params);
  ArrayResize (s, params);
  ArrayInitialize (s, 1);

  // Generate random initial parameter values in given ranges----
  for (int i = 0; i < params; i++)
  {
    x [i] = rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * rand () / 32767.0);
  }

  // Create objects for optimization------------------------------------------
  C_OptimizedFunction  fFunc; fFunc.Init (params, numbTestFuncRuns);
  CObject              obj;
  CNDimensional_Rep    frep;
  CMinBLEICReportShell rep;

  // Set the parameters of the BLEIC optimization algorithm---------------------------
  double diffStep = 0.00001;
  double epsg     = 1e-16;
  double epsf     = 1e-16;
  double epsi     = 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, 0, obj);
  CAlglib::MinBLEICResults      (fFunc.state, x, rep);

  // Output of optimization results-----------------------------------------------
  Print ("BLEIC, best result: ", fFunc.fB, ", number of function launches: ", fFunc.numberLaunches);
}
//——————————————————————————————————————————————————————————————————————————————

由于该方法会自行调用适应度函数(而非通过用户程序调用),因此需要将适应度函数的调用封装在一个继承自ALGLIB中父类的类中(这些父类对于不同的方法而言是不同的)。将封装类声明为C_OptimizedFunction,并在该类中设置以下方法:

1. Func() 是一个虚方法,在派生类中会被重写。
2. Init ()—— 初始化类参数。对于该方法而言:

  • 初始化与函数运行次数和找到的最佳函数值相关的变量。
  • 预留ccB数组用于存储坐标。

 变量:

  • state —— 特定于BLEIC方法的CMinBLEICStateShell类型对象,在调用算法的静态方法和停止方法时使用。
  • numberLaunches —— 当前运行次数(用于防止适应度函数不受控制或运行时间过长)。
  • maxNumbLaunchesAllowed—— 允许的最大运行次数。
  • fB —— 找到的适应度函数的最优值。
  • c [] —— 当前坐标的数组。
  • cB [] —— 用于存储最佳搜索坐标的数组。
//——————————————————————————————————————————————————————————————————————————————
// Class for function optimization, inherits from CNDimensional_Func
class C_OptimizedFunction : public CNDimensional_Func
{
  public:
  C_OptimizedFunction (void) { }
  ~C_OptimizedFunction (void) { }

  // A virtual function to contain the function being optimized--------
  virtual void Func (CRowDouble &x, double &func, CObject &obj);

  // Initialization of optimization parameters---------------------------------------
  void Init (int coords,
             int maxNumberLaunchesAllowed)
  {
    numberLaunches         = 0;
    maxNumbLaunchesAllowed = maxNumberLaunchesAllowed;
    fB = -DBL_MAX;

    ArrayResize (c,  coords);
    ArrayResize (cB, coords);
  }

  //----------------------------------------------------------------------------
  CMinBLEICStateShell state;          // State 
  int                 numberLaunches; // Launch counter 

  double fB;                          // Best found value of the objective function (maximum)
  double cB [];                       // Coordinates of the point with the best function value

  private: //-------------------------------------------------------------------
  double c  [];                       // Array for storing current coordinates
  int    maxNumbLaunchesAllowed;      // Maximum number of function calls allowed
};
//——————————————————————————————————————————————————————————————————————————————

C_OptimizedFunction类的Func方法旨在访问用户的适应度函数。它接受x向量作为参数(这是优化方法提出的问题的优化参数变体之一)、func参数用于接收返回的适应度函数的计算值,以及obj对象(其用途尚不明确,可能用于向该方法传递或从该方法接收额外信息)。该方法的主要阶段:

1. numberLaunches计数器递增。其目的是跟踪Func方法的调用次数。
2. 如果调用次数超过允许的maxNumbLaunchesAllowed值,函数将func值设置为DBL_MAX(“double”类型的最大值,ALGLIB方法旨在最小化函数,此值表示最差的可行解)。然后我们调用MinBLEICRequestTermination函数。它向BLEIC方法发出停止优化的信号。
3. 接下来,在循环中,将x向量中的值复制到c数组中。这是为了使用这些值传递给用户的适应度函数。
4. 调用ObjectiveFunction函数。它计算c数组中当前值的适应度函数值。结果保存在 ffVal中,同时将func值设置为负的ffVal值(我们正在优化一个需要最大化的倒置抛物面,而BLEIC方法用于最小化函数,因此我们将值取反)。
5. 如果当前 ffVal值超过之前的最佳fB值,则更新fB,并将 cB数组复制为c的当前状态。这使我们能够跟踪适应度函数的最优获取值及其对应的参数,并在稍后必要时引用它们。

Func 函数实现了对自定义适应度函数的调用,并跟踪其调用次数,更新最优结果。如果调用次数超过设定的限制,它还会控制停止条件。

//——————————————————————————————————————————————————————————————————————————————
// Implementation of the function to be optimized
void C_OptimizedFunction::Func (CRowDouble &x, double &func, CObject &obj)
{
  // Increase the function launch counter and limitation control----------------
  numberLaunches++;
  if (numberLaunches >= maxNumbLaunchesAllowed)
  {
    func = DBL_MAX;
    CAlglib::MinBLEICRequestTermination (state);
    return;
  }
  
  // Copy input coordinates to internal array-------------------------
  for (int i = 0; i < x.Size (); i++) c [i] = x [i];

  // Calculate objective function value----------------------------------------
  double ffVal = ObjectiveFunction (c);
  func = -ffVal;

  // Update the best solution found--------------------------------------
  if (ffVal > fB)
  {
    fB = ffVal;
    ArrayCopy (cB, c);
  }
}
//——————————————————————————————————————————————————————————————————————————————

在使用BLEIC算法对抛物面函数进行优化并运行测试脚本后,我们在输出中得到了以下结果:

BLEIC,最优结果:0.6861786206145579,函数调用次数:84022

值得注意的是,尽管我们使用了MinBLEICRequestTermination方法请求停止优化,但算法仍然继续执行,并尝试再次访问适应度函数74022次,超出了10000次运行的限制。

现在,我们尝试不限制BLEIC算法,让它自行决定运行。结果如下:

BLEIC,最优结果:1.0,函数调用次数:72017

正如我们所见,BLEIC算法能够完全收敛于抛物面函数,但在这种情况下,我们无法提前估计目标函数所需的运行次数。我们将在后续进行全面的测试和结果分析。

在算法中,微分步长非常重要。例如,如果我们使用非常小的步长,例如1e-16而不是0.00001,那么算法会过早停止,基本上陷入停滞状态,结果如下:

BLEIC,最优结果:0.6615878186651468,函数调用次数:4002


L-BFGS(有限内存Broyden–Fletcher–Goldfarb–Shanno)

L-BFGS(有限内存Broyden–Fletcher–Goldfarb–Shanno)算法是一种高效的优化方法,专为解决变量数量超过1000个的大规模问题而设计。它是一种拟牛顿法,利用有限内存来存储关于函数曲率的信息,从而避免了显式存储和计算完整的Hessian矩阵的需要。

该算法的原理是,它使用最后M对值和梯度来构建并优化被优化函数的二次模型。通常,M是一个适中的数字,范围在3到10之间,这样就显著地将计算成本降低到O(N·M)次操作。在每次迭代中,算法计算当前点的梯度,使用存储的向量确定搜索方向,并执行线性搜索以确定步长。如果拟牛顿法的某一步未能导致函数值或梯度的充分减小,则会发生方向调整的重复。

L-BFGS的主要特点是近似Hessian矩阵的正定性,这保证了拟牛顿法的方向始终是下降方向,无论函数的曲率如何。

LBFGS的基本工作原理:

1. 主要思想:算法试图找到函数的最小值,通过逐渐“下降”到该点,同时构建并不断优化函数的简化(二次)模型。

2. 它是如何具体实现这一点的呢?它记住最后M步(通常是310步),并在每一步存储两件事:

  • 我们之前的位置(即之前的点)
  • 我们可以移动的方向(即梯度)

基于这些数据,算法构建函数曲率的近似(Hessian矩阵),并使用这个近似来确定下一步。

3. 特点:总是向“下”移动(即朝着函数值减小的方向),有效地使用内存(仅存储最后M步),并且运行迅速(成本与问题规模×M成正比)。

4. 实际示例。想象一下您在雾中下山:

  • 您只能在当前点确定下降的方向
  • 记住最后几步以及坡度如何变化
  • 基于这些信息,您可以预测下一步最好走到哪里

5. 局限性:

  • 对于非常复杂的“地形”,它可能运行得更慢
  • 可能需要额外的配置来提高性能

与BLEIC方法不同,L-BFGS允许您直接设置目标函数运行次数的限制,但无法指定待优化参数的边界条件。在以下示例中,M值被设置为“1”,使用其他值并未导致算法性能和行为的显著变化。

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  // Initialization of optimization parameters---------------------------------------
  int numbTestFuncRuns = 10000;
  int params           = 1000;

  // Create and initialize arrays for search range bounds--------------
  double rangeMin [], rangeMax [], rangeStep;
  ArrayResize (rangeMin,  params);
  ArrayResize (rangeMax,  params);

  for (int i = 0; i < params; i++)
  {
    rangeMin  [i] = -10;
    rangeMax  [i] =  10;
  }
  rangeStep =  DBL_EPSILON;

  double x [];
  double s [];
  ArrayResize (x, params);
  ArrayResize (s, params);
  ArrayInitialize (s, 1);

  // Generate random initial parameter values in given ranges-----
  for (int i = 0; i < params; i++)
  {
    x [i] = rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * rand () / 32767.0);
  }

  // Create objects for optimization-------------------------------------------
  C_OptimizedFunction  fFunc; fFunc.Init (params, numbTestFuncRuns);
  CObject              obj;
  CNDimensional_Rep    frep;
  CMinLBFGSReportShell rep;

  // Set the parameters of the L-BFGS optimization algorithm---------------------------
  double diffStep = 0.00001;
  double epsg     = 1e-16;
  double epsf     = 1e-16;

  CAlglib::MinLBFGSCreateF  (1, x, diffStep, fFunc.state);
  CAlglib::MinLBFGSSetCond  (fFunc.state, epsg, epsf, rangeStep, numbTestFuncRuns);
  CAlglib::MinLBFGSSetScale (fFunc.state, s);
  CAlglib::MinLBFGSOptimize (fFunc.state, fFunc, frep, 0, obj);
  CAlglib::MinLBFGSResults  (fFunc.state, x, rep);

  //----------------------------------------------------------------------------
  Print ("L-BFGS, best result: ", fFunc.fB, ", number of function launches: ", fFunc.numberLaunches);
}
//——————————————————————————————————————————————————————————————————————————————

在L-BFGS算法中,“状态”变量类型由CMinLBFGSStateShell所设定。

//——————————————————————————————————————————————————————————————————————————————
// Class for function optimization, inherits from CNDimensional_Func
class C_OptimizedFunction : public CNDimensional_Func
{
  public: //--------------------------------------------------------------------
  C_OptimizedFunction (void) { }
  ~C_OptimizedFunction (void) { }

  // A virtual function to contain the function being optimized---------
  virtual void Func (CRowDouble &x, double &func, CObject &obj);

  // Initialization of optimization parameters----------------------------------------
  void Init (int coords,
             int maxNumberLaunchesAllowed)
  {
    numberLaunches         = 0;
    maxNumbLaunchesAllowed = maxNumberLaunchesAllowed;
    fB = -DBL_MAX;

    ArrayResize (c,  coords);
    ArrayResize (cB, coords);
  }

  //----------------------------------------------------------------------------
  CMinLBFGSStateShell state;          // State 
  int                 numberLaunches; // Launch counter

  double fB;                          // Best found value of the objective function (maximum)
  double cB [];                       // Coordinates of the point with the best function value

  private: //-------------------------------------------------------------------
  double c  [];                       // Array for storing current coordinates
  int    maxNumbLaunchesAllowed;      // Maximum number of function calls allowed
};
//——————————————————————————————————————————————————————————————————————————————

使用MinLBFGSRequestTermination命令请求停止优化过程。

//——————————————————————————————————————————————————————————————————————————————
// Implementation of the function to be optimized
void C_OptimizedFunction::Func (CRowDouble &x, double &func, CObject &obj)
{
  //Increase the function launch counter and limitation control-------------------
  numberLaunches++;
  if (numberLaunches >= maxNumbLaunchesAllowed)
  {
    func = DBL_MAX;
    CAlglib::MinLBFGSRequestTermination (state);
    return;
  }

  // Copy input coordinates to internal array-------------------------
  for (int i = 0; i < x.Size (); i++) c [i] = x [i];

  // Calculate objective function value----------------------------------------
  double ffVal = ObjectiveFunction (c);
  func = -ffVal;

  // Update the best solution found--------------------------------------
  if (ffVal > fB)
  {
    fB = ffVal;
    ArrayCopy (cB, c);
  }
}
//——————————————————————————————————————————————————————————————————————————————

在使用L-BFGS算法对抛物面函数进行优化并运行测试脚本后,我们在输出中得到了以下结果:

L-BFGS,最优结果:0.6743844728276278,函数调用次数:24006

目标函数最大运行次数的参数很可能不起作用,因为算法实际运行次数超过了这个限制的两倍多。

现在,我们让算法在不对运行次数进行限制的情况下执行优化:

L-BFGS,最优结果: 1.0,函数调用次数:52013

与BLEIC一样,L-BFGS能够完全收敛于抛物面函数,但运行次数变得无法控制。在回顾下一个算法时,我们将展示如果不考虑这个细节,这可能会成为一个真正的大问题。

对于L-BFGS来说,微分步长同样重要。如果你使用一个非常小的步长,如1e-16,算法会过早停止,并陷入停滞状态,结果如下:

L-BFGS,最优结果: 0.6746423814003036,函数调用次数:4001


NS(受箱型/线性/非线性非光滑约束的非光滑非凸优化问题)

NS(Nonsmooth Nonconvex Optimization Subject to box/linear/nonlinear - Nonsmooth Constraints)是一种非光滑非凸优化算法,旨在解决目标函数非光滑且非凸的问题。这意味着函数可能具有急剧变化、角度或其他特征。此类问题的主要特点是目标函数可能包含不连续点或突变,这使得使用基于梯度的方法进行分析变得困难。

该算法的原理包括梯度估计,它使用梯度采样方法,此方法涉及在当前解周围的几个随机点估计梯度。这样有助于避免与函数特性相关的问题。基于获得的梯度估计,形成一个受限的二次规划(QP)问题。该问题的解使我们能够确定移动方向,以改进当前解。算法通过迭代工作,在每次迭代中根据计算的梯度和QP问题的解更新当前解。

让我们用简单的语言来考虑一下这种优化描述的含义:

1. 非光滑(非光滑优化):

  • 函数可能存在断裂或“裂隙”
  • 不要求连续可微性
  • 可能存在急剧的过渡和跳跃

2. 非凸(非凸优化):

  • 函数可能具有多个局部最小值和最大值
  • 函数的“地形”可能包含“山峰”和“山谷”

3. 约束类型(CONSTRAINTS):BOX(箱型约束)、LINEAR(线性约束)、NONLINEAR-NONSMOOTH(非线性-非光滑约束,如上所述)。

该方法的特殊性在于需要为AGS求解器(自适应梯度采样方法)指定并设置参数。该求解器旨在解决具有边界、线性和非线性约束的非光滑问题。AGS求解器包含几个重要特性:特殊的约束处理、变量缩放(以处理缩放性差的问题),以及对数值微分的内置支持。

AGS求解器最重要的局限性在于它并非为高维问题而设计。AGS的每一步都需要在随机选择的点上进行大约2·N次梯度评估(相比之下,L-BFGS每步只需要O(1)次评估)。通常,收敛需要O(N)次迭代,这意味着每次优化会话需要进行O(N²)次梯度估计。

与之前的方法不同,NS方法要求边界条件变量使用CRowDouble类型,而不是double类型或问题的优化参数的初始值。此外,还需要为AGS求解器指定参数

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  // Initialization of optimization parameters---------------------------------------
  int numbTestFuncRuns = 10000;
  int params           = 1000;

  // Additionally, you need to specify --------------
  CRowDouble rangeMin, rangeMax;
  rangeMin.Resize (params);
  rangeMax.Resize (params);
  double rangeStep;

  for (int i = 0; i < params; i++)
  {
    rangeMin.Set (i, -10);
    rangeMax.Set (i,  10);
  }
  rangeStep = DBL_EPSILON;

  CRowDouble x, s; 
  x.Resize (params);
  s.Resize (params);
  s.Fill (1);

  // Generate random initial parameter values in given ranges----
  for (int i = 0; i < params; i++)
  {
    x.Set (i, rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * rand () / 32767.0));
  }

  // Create objects for optimization------------------------------------------
  C_OptimizedFunction fFunc; fFunc.Init (params, numbTestFuncRuns);
  CObject             obj;
  CNDimensional_Rep   frep;
  CMinNSReport        rep;
  
  // Set the parameters of the NS optimization algorithm------------------------------
  double diffStep = 0.00001;
  double radius   = 0.8;
  double rho      = 50.0;

  CAlglib::MinNSCreateF    (x, diffStep, fFunc.state);
  CAlglib::MinNSSetBC      (fFunc.state, rangeMin, rangeMax);
  CAlglib::MinNSSetScale   (fFunc.state, s);
  CAlglib::MinNSSetCond    (fFunc.state, rangeStep, numbTestFuncRuns);

  CAlglib::MinNSSetAlgoAGS (fFunc.state, radius, rho);

  CAlglib::MinNSOptimize   (fFunc.state, fFunc, frep, obj);
  CAlglib::MinNSResults    (fFunc.state, x, rep);

  // Output of optimization results-----------------------------------------------
  Print ("NS, best result: ", fFunc.fB, ", number of function launches: ", fFunc.numberLaunches);
}
//——————————————————————————————————————————————————————————————————————————————

对于NS方法,必须创建一个包装类(wrapper class),该类现在继承自另一个父类——CNDimensional_FVec。我们还需要将虚方法更改为FVec。该方法的一个显著特点是,无法返回等于DBL_MAX的适应度函数值,因为在此情况下,该方法将以错误结束,这与之前的优化方法不同。因此,我们将在类中添加一个额外的字段(fW),用于在优化过程中跟踪最坏情况的解。

//——————————————————————————————————————————————————————————————————————————————
// Class for function optimization, inherits from CNDimensional_FVec
class C_OptimizedFunction : public CNDimensional_FVec
{
  public: //--------------------------------------------------------------------
  C_OptimizedFunction (void) { }
  ~C_OptimizedFunction (void) { }

  // A virtual function to contain the function being optimized--------
  virtual void FVec (CRowDouble &x, CRowDouble &fi, CObject &obj);

  // Initialization of optimization parameters---------------------------------------
  void Init (int coords,
             int maxNumberLaunchesAllowed)
  {
    numberLaunches         = 0;
    maxNumbLaunchesAllowed = maxNumberLaunchesAllowed;
    fB = -DBL_MAX;
    fW =  DBL_MAX;

    ArrayResize (c,  coords);
    ArrayResize (cB, coords);
  }

  //----------------------------------------------------------------------------
  CMinNSState state;             // State 
  int         numberLaunches;    // Launch counter

  double fB;                     // Best found value of the objective function (maximum)
  double fW;                     // Worst found value of the objective function (maximum)
  double cB [];                  // Coordinates of the point with the best function value

  private: //-------------------------------------------------------------------
  double c  [];                  // Array for storing current coordinates
  int    maxNumbLaunchesAllowed; // Maximum number of function calls allowed
};
//——————————————————————————————————————————————————————————————————————————————

不正确的操作以红色显示。相反,我们将返回在优化过程中找到的最差解(带上负号,因为该方法用于最小化问题)。当然,在该方法中,我们还需要将停止调用的方法更改为MinNSRequestTermination

//——————————————————————————————————————————————————————————————————————————————
void C_OptimizedFunction::FVec (CRowDouble &x, CRowDouble &fi, CObject &obj)
{
  // Increase the function launch counter and limitation control----------------
  numberLaunches++;
  if (numberLaunches >= maxNumbLaunchesAllowed)
  {
    //fi.Set (0, DBL_MAX);  //Cannot return DBL_MAX value
    fi.Set (0, -fW);
    CAlglib::MinNSRequestTermination (state);
    return;
  }

  // Copy input coordinates to internal array-------------------------
  for (int i = 0; i < x.Size (); i++) c [i] = x [i];

  // Calculate objective function value----------------------------------------
  double ffVal = ObjectiveFunction (c);
  fi.Set (0, -ffVal);

  // Update the best and worst solutions found-----------------------------
  if (ffVal < fW) fW = ffVal;
  if (ffVal > fB)
  {
    fB = ffVal;
    ArrayCopy (cB, c);
  }
}
//——————————————————————————————————————————————————————————————————————————————

在使用NS算法对抛物面函数进行优化并运行测试脚本后,我们在输出中得到了以下结果:

NS,最优结果:0.6605212238333136,函数调用次数:1006503

看起来,目标函数最大允许运行次数的参数对NS算法也不起作用,因为算法实际执行了超过100万次的调用。

现在,我们尝试不对NS算法进行限制,让它在执行优化时不对运行次数进行约束。遗憾的是,我无法等待脚本运行完成,不得不通过关闭图表来强制停止其工作:

无结果

对于NS算法来说,微分步长同样重要。如果我们使用一个非常小的步长,如1e-16,算法会过早停止——它在未使用为其分配的目标函数运行次数的情况下就陷入了停滞状态,结果如下:

NS,最优结果:0.6784901840822722,函数调用次数: 96378


结论

在本文中,我们了解了ALGLIB库中的优化方法。我们探讨了这些方法的关键特性,了解这些特性不仅有助于进行优化本身,还能帮助避免一些意外的情况,例如目标函数调用次数无法控制。

在下一篇,也是关于ALGLIB优化方法的最后一篇文章中,我们将详细探讨另外三种方法。我们将对所有考虑过的方法进行测试,这将有助于在实际应用中识别它们的优点和缺点,并总结研究成果。此外,我们还将按照传统方式可视化算法的运行,以便清晰地展示它们在复杂测试问题上的特征行为。这将使我们更好地理解每种方法如何应对不同的优化挑战。

在文本中提供了运行ALGLIB方法的完整可工作的脚本代码。此外,在归档文件中,您将找到立即开始使用所讨论的方法来优化您的交易策略和其他任务所需的一切。因此,本文的目标——展示方法使用的简单明了示例——已经实现。

我要特别感谢 Evgeniy Chernish,他帮助我理解了访问ALGLIB库方法的具体细节。

文中所用的程序

# 名称 类型 说明
1 简单的ALGLIB BLEIC测试脚本.mq5
脚本
用于测试BLEIC算法的工作脚本
2 简单的ALGLIB L-BFGS测试脚本.mq5
脚本
用于测试L-BFGS算法的工作脚本
3 简单的ALGLIB NS测试脚本.mq5
脚本
用于测试NS(非光滑非凸优化)算法工作的脚本

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/16133

附加的文件 |
最近评论 | 前往讨论 (1)
Rorschach
Rorschach | 25 10月 2024 在 18:03
谢谢,这很有趣。
从基础到中级:SWITCH 语句 从基础到中级:SWITCH 语句
在本文中,我们将学习如何以最简单、最基本的形式使用 SWITCH 语句。此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。
如何使用 Controls 类创建交互式 MQL5 仪表盘/面板(第 2 部分):添加按钮响应。 如何使用 Controls 类创建交互式 MQL5 仪表盘/面板(第 2 部分):添加按钮响应。
在本文中,我们将聚焦于实现按钮的响应,把静态的 MQL5 面板转变为一个交互式工具。我们将探讨如何自动化 GUI 组件的功能,确保它们能够恰当地响应用户的点击操作。最终,我们将建立一个动态界面,提升交互性和交易体验。
您应当知道的 MQL5 向导技术(第 40 部分):抛物线止损和反转(PSAR) 您应当知道的 MQL5 向导技术(第 40 部分):抛物线止损和反转(PSAR)
抛物线止损和反转(PSAR) 是趋势确认、和趋势终结点的指标。因为它在识别趋势方面滞后,所以它的主要目的是为持仓定位尾随止损。然而,我们要探索它是否真的可以当作智能系统的交易信号,这要归功于由向导汇编智能系统的自定义信号类。
构建K线图趋势约束模型(第九部分):多策略EA(第一部分) 构建K线图趋势约束模型(第九部分):多策略EA(第一部分)
今天,我们将探讨如何使用MQL5将多种策略集成到一个EA中。EA不仅仅提供指标和脚本,还允许采用更复杂的交易方法,这些方法能够适应不断变化的市场条件。请阅读本文,带您了解更多。