English Русский Deutsch 日本語 Português
preview
老鹰策略(ES)

老鹰策略(ES)

MetaTrader 5交易 |
39 0
Andrey Dik
Andrey Dik

内容

  1. 引言
  2. 算法的实现
  3. 测试结果


引言

在编程领域,尤其是在开发 EA 时,高效的优化起着至关重要的作用。在庞大的可能选项空间中寻找最优解这类高度专业化的问题,需要运用先进的算法。传统优化方法往往效果不佳,容易陷入局部最优,无法找到全局更优的解。

因此,受自然界启发的元启发式算法,以及经过自然演化的方法,正受到越来越多的关注。这些算法即使在计算速度至关重要的问题中,也能找到良好的、通常是接近最优的解。

在任务日益复杂、资源消耗日益增大的背景下,我们今天将探讨另一种颇具前景的方法:老鹰策略(Eagle Strategy, ES)。该算法受老鹰捕猎行为启发,是一种新型元启发式算法,旨在通过模拟凭视觉搜寻并动态追捕猎物的策略来解决优化问题。


算法的实现

想象一只老鹰在高空翱翔,搜寻猎物。它的捕猎策略效率惊人,分为两个截然不同的阶段:首先,它在高空飞行,以混乱的锯齿状运动扫视广阔区域;一旦发现目标,便迅速俯冲而下,将全部精力集中于特定猎物。正是这种自然智慧构成了老鹰策略算法的基础,该算法于 2010 年被开发出来,用于解决复杂的优化问题。

在第一阶段,算法使用所谓的莱维(Levy)飞行—— 一种描述空间运动的数学模型。与步长大致均匀的典型随机游走不同,莱维飞行由许多小步和穿插其中的罕见但极长的跳跃组成。 

当算法找到一个有前景的区域(就像老鹰发现了猎物),第二阶段便被激活 —— 利用萤火虫算法进行密集的局部搜索。我们曾在其中一篇文章中讨论过这种著名的算法。若干搜索代理如同夜空中的萤火虫,会被更明亮(更成功)的邻居所吸引。萤火虫的亮度对应于它所找到解的质量,而吸引力会随着距离呈指数衰减。这就在探索周边区域与向已知最优解移动之间形成了平衡。

ES策略的核心创新在于这两种模式的周期性交替。在密集的局部搜索之后,算法切换回全局探索,进行一次长距离跳跃,进入搜索空间中一个全新的、未被探索的区域。这防止了过早收敛,即使在存在大量局部最优的极其复杂的解空间中,也能找到全局最优解。

算法的参数直观易懂,配置方便。种群规模决定了同时进行探索的代理数量,莱维参数控制着短跳与长跳的比例,超球半径定义了密集局部搜索的范围,随机化系数则增加了随机性元素以克服局部陷阱。这种灵活性使得无需深入理解数学理论,就能让算法适应特定问题。

ES策略算法的理念简单而优雅:全局视野与局部精准相结合。正如老鹰将观察飞行与定向攻击相结合一样,该算法也在探索未知区域与利用已找到的有前景的解之间取得平衡。

该算法的关键特性包括:随着解的质量提升自动在各阶段之间切换、自适应参数(λ 在停滞期减小,步长随时间递减),以及在探索新区域与利用已找到的解之间保持平衡。

老鹰算法

图 1. ES 算法示意

图示展示了算法的两个运行阶段:莱维飞行轨迹(红色虚线)、局部搜索超球(蓝色圆圈)、球内萤火虫的运动(带光晕的绿色点),以及优化函数的等高线。

让我们来准备伪代码。

初始化:
1. 创建一个随机位置的代理种群
2. 设置全局搜索标志(阶段 = 全局)
3. 初始化停滞计数器和进度计数器

主循环(直到达到停止准则):
  
如果(阶段 = 全局):
对每个代理:
- 使用曼特尼亚算法生成莱维步长
      - 计算自适应步长缩放系数(初期较大,后期较小)
      - 更新位置:新位置 = 当前位置 + 莱维步长 × 缩放系数
      - 应用边界约束
    
如果(全局最优解得到改进):
      - 切换到局部阶段
      - 找到最优代理作为局部搜索中心
      - 重置停滞计数器
    否则:
      - 增加停滞计数器
      - 如果(停滞 > 5 次迭代):
        - 减小 λ 参数,进行更激进的飞行
  
否则(阶段 = 局部):
以 80% 的概率:
      - 识别位于最优代理周围、半径为0.1的超球内的代理
      - 如果(代理数量 < 5):
        - 取 5 个最近邻居或种群的 30%
      
      对局部组中的每个代理:
        对组中其他每个代理:
          如果(另一个代理更优):
            - 计算吸引力 β = β₀ × exp (-γ × 距离 ²)
            - 移动代理:位置 += β × (最优 - 当前) + 随机噪声
    
    以 20% 的概率:
      - 将全局最优代理的部分坐标复制给若干随机代理
    
    - 增加局部迭代计数器
    - 如果(完成 20 次局部迭代):
       - 返回全局阶段
       - 恢复 λ 的原始参数

  更新:
    对每个代理:
      - 计算适应度
      - 更新个人最优
      - 更新全局最优

在老鹰策略算法的实现中,采用了一种数值方法来生成莱维分布的随机数:曼特尼亚算法。该算法由 R. N. 曼特尼亚于 1994 年提出,现已成为优化算法中模拟莱维飞行的标准方法。曼特尼亚从数学上证明,两个经过特殊缩放的高斯变量的比值,在实际应用的重要取值范围内,会产生与莱维分布非常接近的分布。其核心思想如下:

// Calculating sigma for Mantegna algorithm
double numerator   = Gamma(1.0 + lambda) * MathSin(M_PI * lambda / 2.0);
double denominator = Gamma((1.0 + lambda) / 2.0) * lambda * MathPow(2.0, (lambda - 1.0) / 2.0);
double sigma = MathPow(numerator / denominator, 1.0 / lambda);

// Generate u and v from normal distributions
double u_val = GenerateGaussian() * sigma;
double v_val = MathAbs(GenerateGaussian());

// Calculate Levy step
levyStep[c] = u_val / MathPow(v_val, 1.0 / lambda);

取两个随机变量:

  • u — 来自 N (0, σ²) 正态分布
  • v — 来自 N (0, 1) 正态分布
σ 的特殊方程:
σ = [Γ(1+λ) × sin(πλ/2) / (Γ((1+λ)/2) × λ × 2^((λ-1)/2))]^(1/λ)

其中 Γ 为伽马函数,λ 为莱维分布参数(1 < λ ≤ 3)。

需要注意的是,在确定伽马函数(用于计算曼特尼亚方法中的 σ 参数)时,使用了兰索斯近似法,这是一种计算伽马函数的高精度数值方法。这是计算 Γ(z) 最高效的方法之一,在代码中作为单独函数实现,我们将在最后讨论。

基本方程如下:

Γ(z+1) = √(2π) × ((z+g+0.5)^(z+0.5)) × e^(-(z+g+0.5)) × Ag(z)

其中:g 为参数(通常取 7);Ag (z) 为预计算系数的级数,当 g=7 且使用 9 个系数时,可达到约 15 位有效数字的精度。

对于 z < 0.5 的情况,反射公式利用该比值,使得可以计算所有实数的伽马函数:

Γ(z) × Γ(1-z) = π / sin(πz)

如果没有高效的伽马函数计算方法,生成莱维飞行的计算成本会很高,这将显著拖慢整个优化算法的速度。

莱维最终步长

step = u / |v|^(1/λ)

该算法生成的步长序列具有莱维飞行的典型行为特征:大量小步长(局部探索)、罕见但极大的跳跃(全局探索),以及步长服从幂律分布。

这种方法的优势在于解决方案的简洁性 —— 因为只需要一个正态分布生成器,同时兼具算法的稳定性和数值鲁棒性。正是这一特性使得莱维飞行在优化中如此有效 —— 它们在局部区域的细致探索与向搜索空间新区域的快速转移之间提供了最佳平衡。

在详细研究了老鹰策略算法所使用的方法之后,我们可以信心满满地着手编写 C_AO_ES 类了,该类代表了基于老鹰捕猎策略的优化方法实现,并继承自 C_AO 基类。该方法采用两阶段策略:首先执行全局搜索以识别潜在的有前景区域,然后在选定的搜索区域内进行局部搜索以细化结果。

种群大小(popSize) 指定了参与搜索的 "候选" 解的数量。莱维参数(lambda) 控制随机步长的分布。超球半径(sphereRadius) 定义了局部搜索的范围。局部搜索迭代次数(localIterations) 指定了在超球内对解进行多少次细化。随机化参数(alpha)和吸引力参数(beta0) 调节搜索模型的各个组成部分,例如随机运动和 "光" 的影响(用比喻的说法)。 

全局探索阶段(GlobalExploration)专注于在整个搜索空间中寻找 "有前景的" 区域,使用由莱维分布生成的随机步长。这种方法能够实现广泛的探索,并且能很好地适应大型搜索空间。

局部开发阶段(LocalExploitation)在选定点周围的超球内执行更细致的搜索。在这种情况下,使用更小、更精确的步长,对应于局部优化。

莱维步长生成(GenerateLevyStep)使用莱维分布生成随机移动,这在搜索空间中同时提供小步和大步跳跃,以平衡探索与开发。

该类包含跟踪搜索进度的机制,例如记住找到的最优解、停滞计数器(当解没有改进时),以及通过时间或迭代次数限制算法运行的世代循环、随机步长的分布参数计算(特别是高斯分布和莱维分布的生成),以及搜索阶段管理方法 —— 确保全局阶段和局部阶段之间的切换及其运行控制。

总的来说,该方法呈现了一种搜索策略:先对空间进行广泛探索以识别潜在区域,再进行细致的局部优化以获得精确解,同时通过各种参数和机制使其行为能够适应特定的问题和条件。

//————————————————————————————————————————————————————————————————————
class C_AO_ES : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_ES () { }
  C_AO_ES ()
  {
    ao_name = "ES";
    ao_desc = "Eagle Strategy";
    ao_link = "https://www.mql5.com/en/articles/18460";

    popSize         = 100;   // population size
    lambda          = 1.0;  // Levy distribution parameter (1 < λ ≤ 3)
    sphereRadius    = 0.1;  // hypersphere radius for local search
    localIterations = 20;   // number of local search iterations
    alpha           = 0.1;  // randomization parameter for Firefly
    beta0           = 1.2;  // initial attractiveness

    ArrayResize (params, 6);

    params [0].name = "popSize";         params [0].val = popSize;
    params [1].name = "lambda";          params [1].val = lambda;
    params [2].name = "sphereRadius";    params [2].val = sphereRadius;
    params [3].name = "localIterations"; params [3].val = localIterations;
    params [4].name = "alpha";           params [4].val = alpha;
    params [5].name = "beta0";           params [5].val = beta0;
  }

  void SetParams ()
  {
    popSize         = (int)params [0].val;
    lambda          = params      [1].val;
    sphereRadius    = params      [2].val;
    localIterations = (int)params [3].val;
    alpha           = params      [4].val;
    beta0           = params      [5].val;
  }

  bool Init (const double &rangeMinP  [],  // minimum values
             const double &rangeMaxP  [],  // maximum values
             const double &rangeStepP [],  // step change
             const int     epochsP = 0);   // number of epochs

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  double lambda;          // Levy distribution parameter (1 < λ ≤ 3)
  double sphereRadius;    // hypersphere radius for local search
  int    localIterations; // number of local search iterations
  double alpha;           // randomization parameter
  double beta0;           // initial attractiveness

  private: //---------------------------------------------------------
  double gamma_es;           // light absorption coefficient
  double levyStep [];        // array for Levy steps

  // Phase tracking
  bool   inLocalSearchPhase; // local search flag
  int    localSearchCenter;  // local search center
  int    localSearchCounter; // local search iteration counter

  // Monitoring convergence
  double prevBestFitness;    // previous best value
  int    stagnationCounter;  // stagnation counter

  // Tracking epochs
  int    epochCurrent;       // current epoch
  int    epochMax;           // maximum number of epochs

  // Auxiliary methods
  void   GlobalExploration  ();
  void   LocalExploitation  ();
  void   GenerateLevyStep   ();
  double GenerateGaussian   ();
  double Gamma              (double z);
};
//————————————————————————————————————————————————————————————————————

C_AO_ES 类的 Init 方法在开始搜索前初始化优化算法。首先调用 StandardInit 方法,该方法负责算法的标准初始化。它配置通用参数和数据结构。如果 StandardInit 失败,则整个方法返回 'false',表示初始化未成功。

接下来,为 levyStep 数组分配内存,其大小等于坐标 "coords" 的数量。该数组将用于存储根据莱维分布生成的步长。inLocalSearchPhase 标志设置为 'false',算法初始处于全局搜索阶段。localSearchCenter 和 localSearchCounter 变量设置为 "0",为局部搜索准备计数器。

收敛参数初始化:

  • prevBestFitness 设置为最小可能值,以确保找到的第一个解一定被认为比前一个解更好。
  • stagnationCounter 设置为 "0",用于跟踪未改进已找到最优解的迭代次数。

世代参数初始化:

  • epochMax 被赋予输入参数 epochsP 的值,该参数设置算法的最大世代数(迭代次数)。
  • epochCurrent 设置为 "0",用于跟踪当前世代。

设置固定参数: 将 "1.0" 的值赋给 gamma_es 变量。该参数用于萤火虫算法,而萤火虫算法是整体策略的一部分。

初始化由 "a" 个解组成的初始种群。对于种群中的每个解:

  • 解向量的每个坐标(a [i].c [c])用从 [rangeMin [c], rangeMax [c]] 区间中取得的随机值进行初始化。
  • 每个坐标的值被 "四舍五入" 到最接近的可接受值,考虑步长 (rangeStep [c]),使用 u.SeInDiSp 方法。
  • 每个解的目标函数值(a [i].f 和 a [i].fB)设置为 "-DBL_MAX"。
//————————————————————————————————————————————————————————————————————
bool C_AO_ES::Init (const double &rangeMinP  [],
                    const double &rangeMaxP  [],
                    const double &rangeStepP [],
                    const int     epochsP = 0)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //------------------------------------------------------------------
  // Initialize arrays
  ArrayResize (levyStep, coords);

  // Initialize phase tracking
  inLocalSearchPhase = false;
  localSearchCenter  = 0;
  localSearchCounter = 0;

  // Initialize convergence tracking
  prevBestFitness   = -DBL_MAX;
  stagnationCounter = 0;

  // Initialize epoch tracking
  epochMax     = epochsP;
  epochCurrent = 0;

  // Fixed Firefly parameter
  gamma_es = 1.0;

  // Initialize the population randomly
  for (int i = 0; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
      a [i].c [c] = u.SeInDiSp  (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }

    a [i].f  = -DBL_MAX;
    a [i].fB = -DBL_MAX;
  }

  return true;
}
//————————————————————————————————————————————————————————————————————

Moving 方法实现了迭代优化的基本逻辑,在全局搜索和局部开发之间交替进行。每次调用该方法都会增加世代计数器,以跟踪算法的进度。

搜索阶段处理:
  • 如果当前阶段是全局搜索,则使用以莱维分布为模型的步长来探索空间,在整个空间中生成新的解,以寻找具有潜力的新区域;

  • 如果全局搜索后发现了更优解,算法则切换到局部阶段。在这种情况下,选择最有前景的解(代理),围绕它进行搜索以细化该解;

  • 如果多次迭代都没有出现改进(停滞累积),则通过更激进的飞行步长来增强全局探索力度,减小 "lambda" 参数以实现更广泛的空间探索;

  • 如果算法处于局部搜索阶段,则有 80% 的概率使用模拟萤火虫算法的方法执行局部优化。在这种情况下,对选定的解进行细化,并增加局部迭代计数器。达到指定的局部搜索迭代次数后,算法返回全局搜索。 

额外的随机扰动以一定概率改变种群的解,旨在保持多样性并防止陷入局部 "陷阱"。

    因此,该方法实现了一种逐步且灵活的搜索策略,在全局探索和定向局部优化之间交替进行。这使得在探索新区域与改进现有解之间取得平衡成为可能。

    //————————————————————————————————————————————————————————————————————
    void C_AO_ES::Moving ()
    {
      epochCurrent++;
    
      // PHASE DECISION: Alternating between global and local search
      if (!inLocalSearchPhase)
      {
        // PHASE 1: GLOBAL EXPLORATION using Levy flights
        GlobalExploration ();
    
        // Check whether it is necessary to switch to local search
        // Switch if we find a promising area (improving the best fitness)
        if (fB > prevBestFitness)
        {
          inLocalSearchPhase = true;
          localSearchCounter = 0;
          prevBestFitness    = fB;
          stagnationCounter  = 0;
    
          // Find the best agent to center local search
          localSearchCenter = 0;
          double bestFit = -DBL_MAX;
    
          for (int i = 0; i < popSize; i++)
          {
            if (a [i].f > bestFit)
            {
              bestFit = a [i].f;
              localSearchCenter = i;
            }
          }
        }
        else
        {
          stagnationCounter++;
    
          // In case of stagnation, increase exploration
          if (stagnationCounter > 5)
          {
            lambda = MathMax (1.0, lambda - 0.1); // Make Levy flights more aggressive
          }
        }
      }
      else
      {
        if (u.RNDprobab () < 0.8)
        {
          // PHASE 2: LOCAL EXPLOITATION using the Firefly algorithm
          LocalExploitation ();
    
          localSearchCounter++;
    
          // Return to global search after local iterations are complete
          if (localSearchCounter >= localIterations)
          {
            inLocalSearchPhase = false;
            lambda = params [1].val; // Reset lambda to its original value
          }
        }
        else
        {
          for (int i = 0; i < popSize; i++)
          {
            for (int c = 0; c < coords; c++)
            {
              if (u.RNDprobab () < 0.5)
              {
                a [i].c [c] = cB [c];
              }
            }
          }
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    Revision 方法用于在算法运行过程中更新关于最优解的信息。该方法遍历当前解种群的所有元素,对每个解将其当前适应度与存储的个人最优结果进行比较。如果当前值更优,则更新个人最优结果及对应的解(保存当前迭代的最优解)。

    然后,该方法将每个解的适应度与在整个种群中找到的当前全局最优值进行比较。如果当前结果是最优的,则更新全局结果并存储对应的解。因此,该方法在算法当前状态下维护关于局部和全局最优解的最新信息,确保每一步都保留找到的最优解。

    //————————————————————————————————————————————————————————————————————
    void C_AO_ES::Revision ()
    {
      for (int i = 0; i < popSize; i++)
      {
        // Updating the personal best one
        if (a [i].f > a [i].fB)
        {
          a [i].fB = a [i].f;
          ArrayCopy (a [i].cB, a [i].c, 0, 0, WHOLE_ARRAY);
        }
    
        // Update the global best one
        if (a [i].f > fB)
        {
          fB = a [i].f;
          ArrayCopy (cB, a [i].c, 0, 0, WHOLE_ARRAY);
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    GlobalExploration 方法实现了使用莱维飞行来探索解空间的全局探索阶段。在循环中对每个解,使用莱维分布为整个种群生成随机步长,该步长决定了解空间中移动的方向和长度。

    莱维分布的特点是具有 "重尾",这使得它既能进行小的细化步长,也能进行大而罕见的跳跃来探索遥远的区域。在循环中,对解的每个坐标计算以下内容:

    • 搜索范围(坐标最大值与最小值之差),
    • stepScale 自适应缩放系数。它随着搜索的进行而减小(越接近结束,步长越小),有助于在有前景的解周围收窄搜索范围,而在搜索初期步长较大,以进行更广泛的探索。
    • 应用莱维步长:当前解的坐标按与莱维步长、搜索跨度和缩放系数成比例的量进行改变。
    • 边界修正:检查新坐标是否超出允许范围;如果超出,则调整值以保持在指定范围内。
    最终,该方法使用从莱维分布导出的步长更新种群中每个解的位置,从而实现解空间的全局探索,并根据优化进度自适应控制步长大小。这使我们能够在初期探索空间,并在向最优解靠近的过程中逐步细化结果。
    //————————————————————————————————————————————————————————————————————
    // PHASE 1: GLOBAL EXPLORATION using Levy flights
    void C_AO_ES::GlobalExploration ()
    {
      for (int i = 0; i < popSize; i++)
      {
        // Generate Levy step
        GenerateLevyStep ();
    
        // Update position using Levy flight
        for (int c = 0; c < coords; c++)
        {
          double range = rangeMax [c] - rangeMin [c];
    
          // Adaptive step size depending on search progress
          double progress = (epochMax > 0) ? (double)epochCurrent / (double)epochMax : 0.5;
          double stepScale = 0.01 + 0.2 * (1.0 - progress); // Start with big steps
    
          // Apply the Levy step
          a [i].c [c] += levyStep [c] * range * stepScale;
    
          // Boundary restrictions
          a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    LocalExploitation 方法实现了使用萤火虫算法对解进行局部改进的阶段。在初始阶段,确定位于最优解周围给定超球内的代理。为此,计算每个代理到超球中心的距离,如果距离小于半径,或者该代理就是搜索中心,则将其纳入选定组。

    如果超球内的代理太少,则通过纳入最近邻居来扩大搜索范围。为此,计算所有代理到中心的距离,并选择最近的代理 —— 可以按数量(例如 5 个)或按种群比例(例如 30%)。这些代理被添加到组中。

    萤火虫算法的应用范围:选定的代理按照算法规则进行交互:
    • 对每个代理,检查组中是否存在适应度更优的其他代理。
    • 如果存在,则当前代理向更具吸引力(更优)的代理移动,并计算它们之间的距离。
    • 另一个代理的吸引力越强,其 "亮度" 就越高(取决于距离和算法参数)。
    • 移动过程结合了向更优代理的吸引力与随机扰动,以保持多样性。
    • 移动之后,检查坐标边界以确保解保持在可接受范围内。

      因此,该方法利用代理之间按照萤火虫算法策略模型的交互,在已找到的最优点附近实现解的局部改进:最优解吸引较差的解,促进了解空间的点状优化。

      //————————————————————————————————————————————————————————————————————
      // PHASE 2: Local exploitation using the Firefly algorithm
      void C_AO_ES::LocalExploitation ()
      {
        // Identification of agents inside the hypersphere around the best solution
        double agents_in_sphere [];
        ArrayResize (agents_in_sphere, 0);
      
        for (int i = 0; i < popSize; i++)
        {
          double normalized_dist = 0.0;
      
          for (int c = 0; c < coords; c++)
          {
            double diff = (a [i].c [c] - a [localSearchCenter].c [c]) / (rangeMax [c] - rangeMin [c]);
            normalized_dist += diff * diff;
          }
          normalized_dist = MathSqrt (normalized_dist);
      
          // Include agents inside the sphere or the center itself
          if (normalized_dist <= sphereRadius || i == localSearchCenter)
          {
            int size = ArraySize (agents_in_sphere);
            ArrayResize (agents_in_sphere, size + 1);
            agents_in_sphere [size] = i;
          }
        }
      
        // If there are too few agents, expand to the nearest neighbors
        if (ArraySize (agents_in_sphere) < 5)
        {
          ArrayResize (agents_in_sphere, 0);
      
          // Calculate distances for all agents
          double distances [];
          ArrayResize (distances, popSize);
      
          for (int i = 0; i < popSize; i++)
          {
            if (i == localSearchCenter)
            {
              distances [i] = 0.0;
            }
            else
            {
              double dist = 0.0;
              for (int c = 0; c < coords; c++)
              {
                double diff = (a [i].c [c] - a [localSearchCenter].c [c]) / (rangeMax [c] - rangeMin [c]);
                dist += diff * diff;
              }
              distances [i] = MathSqrt (dist);
            }
          }
      
          // Take the closest 5 agents or 30% of the population
          int numAgents = MathMin (popSize, MathMax (5, popSize / 3));
          ArrayResize (agents_in_sphere, numAgents);
      
          // Simple selection of nearby agents
          for (int k = 0; k < numAgents; k++)
          {
            double minDist = DBL_MAX;
            int minIdx = -1;
      
            for (int i = 0; i < popSize; i++)
            {
              bool already_selected = false;
      
              for (int j = 0; j < k; j++)
              {
                if (agents_in_sphere [j] == i)
                {
                  already_selected = true;
                  break;
                }
              }
      
              if (!already_selected && distances [i] < minDist)
              {
                minDist = distances [i];
                minIdx = i;
              }
            }
      
            agents_in_sphere [k] = minIdx;
          }
        }
      
        // Execute the Firefly algorithm on the selected agents
        int numLocalAgents = ArraySize (agents_in_sphere);
      
        for (int i = 0; i < numLocalAgents; i++)
        {
          int idx_i = (int)agents_in_sphere [i];
      
          for (int j = 0; j < numLocalAgents; j++)
          {
            if (i == j) continue;
      
            int idx_j = (int)agents_in_sphere [j];
      
            // If agent j is better than agent i, move i to j
            if (a [idx_j].f > a [idx_i].f)
            {
              // Calculate the distance
              double r_squared = 0.0;
      
              for (int c = 0; c < coords; c++)
              {
                double diff = (a [idx_j].c [c] - a [idx_i].c [c]) / (rangeMax [c] - rangeMin [c]);
                r_squared += diff * diff;
              }
      
              // Calculate attractiveness
              double beta = beta0 * MathExp (-gamma_es * r_squared);
      
              // Move agent i to agent j
              for (int c = 0; c < coords; c++)
              {
                double range = rangeMax [c] - rangeMin [c];
      
                // Firefly motion equation
                a [idx_i].c [c] += beta * (a [idx_j].c [c] - a [idx_i].c [c]) +
                                   alpha * (u.RNDfromCI (-0.5, 0.5)) * range * 0.1;
      
                // Apply borders
                a [idx_i].c [c] = u.SeInDiSp (a [idx_i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
              }
            }
          }
        }
      }
      //————————————————————————————————————————————————————————————————————
      

      GenerateLevyStep 方法负责生成在优化算法中实现全局探索策略所需的莱维步长。该方法使用曼特尼亚算法来生成这些步长,这是我们在上面已经讨论过的方法。

      计算 sigma:

      代码中的公式计算 "sigma" 参数。该参数与莱维分布的尺度有关,并取决于常数 "lambda"——lambda 表征了莱维分布的形状(决定分布尾部的 "厚重" 程度)。Gamma () 是伽马函数,是阶乘对实数的推广(该方法的代码将在下面给出)。该参数用于生成正态分布的值,这些值随后用于曼特尼亚算法。

      莱维步长的生成是针对解的每个坐标(参数)独立进行的。从正态(高斯)分布生成两个随机变量 u_val 和 v_val,取 v_val 的绝对值。莱维步长 "levyStep [c]" 使用曼特尼亚算法公式计算:u_val / Math.pow (v_val, 1.0 /lambda)。执行检查以避免除以零(如果 v_val 非常小)。莱维步长的绝对值受到限制。这样做是为了防止过大的跳跃。

      该方法的结果是生成 levyStep 数组,包含每个坐标的莱维步长值。这些步长随后用于 GlobalExploration 方法中,以更新搜索空间中每个解的位置。

      //————————————————————————————————————————————————————————————————————
      // Generate Levy step using Mantegna algorithm
      void C_AO_ES::GenerateLevyStep ()
      {
        // Calculate sigma for Mantegna algorithm
        double numerator   = Gamma (1.0 + lambda) * MathSin (M_PI * lambda / 2.0);
        double denominator = Gamma ((1.0 + lambda) / 2.0) * lambda * MathPow (2.0, (lambda - 1.0) / 2.0);
        double sigma = MathPow (numerator / denominator, 1.0 / lambda);
      
        for (int c = 0; c < coords; c++)
        {
          // Generate u and v from normal distributions
          double u_val = GenerateGaussian () * sigma;
          double v_val = MathAbs (GenerateGaussian ());
      
          // Calculate the Levy step
          if (v_val > 1e-10)
          {
            levyStep [c] = u_val / MathPow (v_val, 1.0 / lambda);
          }
          else
          {
            levyStep [c] = 0.0;
          }
      
          // Limit extreme values
          if (MathAbs (levyStep [c]) > 10.0)
          {
            levyStep [c] = 10.0 * (levyStep [c] > 0 ? 1.0 : -1.0);
          }
        }
      }
      //————————————————————————————————————————————————————————————————————
      

      GenerateGaussian 方法实现了生成服从均值为 "0"、标准差为 "1" 的正态(高斯)分布的随机数。它使用 Box-Muller 变换,这是解决该问题的一种相当常见的方法,也是我们在之前的文章中已经使用过的方法。

      使用静态变量 "hasSpare" 和 "spare",Box-Muller 变换一次生成两个独立的正态分布随机数,'hasSpare' 是一个逻辑变量,用于确定是否将生成的其中一个数保存起来供下次函数调用使用,'spare' 是存储这个 "备用" 数的变量。

        生成新的随机数(如有必要):

        如果没有 "备用" 数,则:从区间 (0, 1) 上的均匀分布生成两个独立的随机变量 u_val 和 v_val。u.RNDfromCI 函数生成均匀分布的数。应用 Box-Muller 变换:

        • "mag"(幅值)计算为 - 2.0 * Math.log (u_val + 1e-10) 的平方根。向 u_val 添加一个小数是为了避免对零取对数,这是不可接受的。
        • "备用" 数计算为 mag * Math.cos (2.0 * M_PI * v_val)。
        • 返回的值为 mag * Math.sin (2.0 * M_PI * v_val)。

        该方法返回一个服从正态分布的随机数。
        //————————————————————————————————————————————————————————————————————
        // Generate a Gaussian random number using the Box-Muller transform
        double C_AO_ES::GenerateGaussian ()
        {
          static bool hasSpare = false;
          static double spare;
        
          if (hasSpare)
          {
            hasSpare = false;
            return spare;
          }
        
          hasSpare = true;
          double u_val = u.RNDfromCI (0.0, 1.0);
          double v_val = u.RNDfromCI (0.0, 1.0);
        
          double mag = MathSqrt (-2.0 * MathLog (u_val + 1e-10));
          spare = mag * MathCos (2.0 * M_PI * v_val);
        
          return mag * MathSin (2.0 * M_PI * v_val);
        }
        //————————————————————————————————————————————————————————————————————
        

        Gamma 方法是一个对给定参数 "z" 近似计算伽马函数的函数。因为伽马函数是一个重要的数学函数,尤其在统计学和优化中,但其精确计算可能很困难且耗时,所以通常使用近似方法。我们使用兰索斯(Lanczos)近似法,它以相对较低的计算成本提供了良好的精度。 让我们讨论一下要点。

        如果 "z" 参数小于 "0.5",则应用欧拉反射公式。这使得可以计算接近零的参数的伽马函数。反射公式将 "z" 的伽马函数与 "1 - z" 的伽马函数联系起来。我们将使用固定的兰索斯系数,这些系数是经过精心选择以确保高近似精度的,以及包含幂函数和指数函数的系数和公式。该方法返回给定参数 "z" 的伽马函数近似值。

        兰索斯近似法提供了良好的精度,足以满足大多数实际应用。该算法相对高效,因为它只需要少量算术运算和系数查表。它适用于广泛的参数取值范围,尤其是与反射公式结合使用时。总的来说,Gamma 方法是一种相当准确的伽马函数近似计算方法。

        //————————————————————————————————————————————————————————————————————
        // Approximation of the gamma function using Lanczos approximation
        double C_AO_ES::Gamma (double z)
        {
          if (z < 0.5)
          {
            // Reflection formula for z < 0.5
            return M_PI / (MathSin (M_PI * z) * Gamma (1.0 - z));
          }
        
          // Lanczos coefficients
          const double g = 7.0;
          double coef [] =
          {
            0.99999999999980993,
            676.5203681218851,
            -1259.1392167224028,
            771.32342877765313,
            -176.61502916214059,
            12.507343278686905,
            -0.13857109526572012,
            9.9843695780195716e-6,
            1.5056327351493116e-7
          };
        
          z -= 1.0;
          double x = coef [0];
        
          for (int i = 1; i < 9; i++)
          {
            x += coef [i] / (z + i);
          }
        
          double t = z + g + 0.5;
          double sqrt2pi = MathSqrt (2.0 * M_PI);
        
          return sqrt2pi * MathPow (t, z + 0.5) * MathExp (-t) * x;
        }
        //————————————————————————————————————————————————————————————————————
        


        测试结果

        尽管该算法未能进入我们的排名表,但其结果值得关注。

        ES|老鹰算法|100.0|1.0|0.1|20.0|0.1|1.2|
        =============================
        5个Hilly测试函数;运行次数:10000;结果:0.7077570016043803
        25个Hilly测试函数;运行次数:10000;结果:0.4307775904381953
        500个Hilly测试函数;运行次数:10000;结果:0.2747545259235643
        =============================
        5个Forest测试函数;运行次数:10000;结果:0.7173720280531499
        25个Forest测试函数;运行次数:10000;结果:0.3448423301485268
        500 个Forest测试函数;运行次数:10000;结果:0.1597390810339928
        =============================
        5个Megacity测试函数;运行次数:10000;结果:0.5184615384615384
        25个Megacity测试函数;运行次数:10000;结果:0.2775384615384615
        500个Megacity测试函数;运行次数:10000;结果:0.11063076923077016
        =============================
        总分:3.54187(39.35%)

        可视化结果清晰地展示了搜索阶段如何按照策略划分,以及长距离跳跃和短距离细化移动分别在何时发生。

        Hilly

        ES策略在Hilly测试函数上的表现

        Forest

        ES策略在Forest测试函数上的表现

        Megacity

        ES策略在Megacity测试函数上的表现

        ES算法被列入排名表以供参考。

        # 算法 说明 Hilly Hilly
        最终结果
        Forest Forest
        最终结果
        Megacity (离散) Megacity
        最终结果
        最终
        结果
        % of
        MAX
        10 p (5 F) 50 p (25 F) 1000 p (500 F) 10 p (5 F) 50 p (25 F) 1000 p (500 F) 10 p (5 F) 50 p (25 F) 1000 p (500 F)
        1 ANS 交叉邻近搜索 0.94948 0.84776 0.43857 2.23581 1.00000 0.92334 0.39988 2.32323 0.70923 0.63477 0.23091 1.57491 6.134 68.15
        2 CLA 代码锁定算法(JOO) 0.95345 0.87107 0.37590 2.20042 0.98942 0.91709 0.31642 2.22294 0.79692 0.69385 0.19303 1.68380 6.107 67.86
        3 AMOm 动物迁徙优化M 0.90358 0.84317 0.46284 2.20959 0.99001 0.92436 0.46598 2.38034 0.56769 0.59132 0.23773 1.39675 5.987 66.52
        4 (P+O)ES (P+O) 进化策略 0.92256 0.88101 0.40021 2.20379 0.97750 0.87490 0.31945 2.17185 0.67385 0.62985 0.18634 1.49003 5.866 65.17
        5 CTA 彗尾算法(JOO) 0.95346 0.86319 0.27770 2.09435 0.99794 0.85740 0.33949 2.19484 0.88769 0.56431 0.10512 1.55712 5.846 64.96
        6 TETA 时间演化旅行算法(JOO) 0.91362 0.82349 0.31990 2.05701 0.97096 0.89532 0.29324 2.15952 0.73462 0.68569 0.16021 1.58052 5.797 64.41
        7 SDSm 随机扩散搜索 M 0.93066 0.85445 0.39476 2.17988 0.99983 0.89244 0.19619 2.08846 0.72333 0.61100 0.10670 1.44103 5.709 63.44
        8 BOAm 台球优化算法 M 0.95757 0.82599 0.25235 2.03590 1.00000 0.90036 0.30502 2.20538 0.73538 0.52523 0.09563 1.35625 5.598 62.19
        9 AAm 射箭算法 M 0.91744 0.70876 0.42160 2.04780 0.92527 0.75802 0.35328 2.03657 0.67385 0.55200 0.23738 1.46323 5.548 61.64
        10 ESG 社会群体的进化(JOO) 0.99906 0.79654 0.35056 2.14616 1.00000 0.82863 0.13102 1.95965 0.82333 0.55300 0.04725 1.42358 5.529 61.44
        11 SIA 模拟各向同性退火(JOO) 0.95784 0.84264 0.41465 2.21513 0.98239 0.79586 0.20507 1.98332 0.68667 0.49300 0.09053 1.27020 5.469 60.76
        12 BBO 基于生物地理学的优化算法 0.94912 0.69456 0.35031 1.99399 0.93820 0.67365 0.25682 1.86867 0.74615 0.48277 0.17369 1.40261 5.265 58.50
        13 ACS 人工协同搜索 0.75547 0.74744 0.30407 1.80698 1.00000 0.88861 0.22413 2.11274 0.69077 0.48185 0.13322 1.30583 5.226 58.06
        14 DA 辩证算法 0.86183 0.70033 0.33724 1.89940 0.98163 0.72772 0.28718 1.99653 0.70308 0.45292 0.16367 1.31967 5.216 57.95
        15 BHAm 黑洞算法 M 0.75236 0.76675 0.34583 1.86493 0.93593 0.80152 0.27177 2.00923 0.65077 0.51646 0.15472 1.32195 5.196 57.73
        16 ASO 无政府社会优化 0.84872 0.74646 0.31465 1.90983 0.96148 0.79150 0.23803 1.99101 0.57077 0.54062 0.16614 1.27752 5.178 57.54
        17 RFO 皇家同花顺优化算法 (JOO) 0.83361 0.73742 0.34629 1.91733 0.89424 0.73824 0.24098 1.87346 0.63154 0.50292 0.16421 1.29867 5.089 56.55
        18 AOSm 原子轨道搜索 M 0.80232 0.70449 0.31021 1.81702 0.85660 0.69451 0.21996 1.77107 0.74615 0.52862 0.14358 1.41835 5.006 55.63
        19 TSEA 龟壳进化算法(JOO) 0.96798 0.64480 0.29672 1.90949 0.99449 0.61981 0.22708 1.84139 0.69077 0.42646 0.13598 1.25322 5.004 55.60
        20 DE 差分进化 0.95044 0.61674 0.30308 1.87026 0.95317 0.78896 0.16652 1.90865 0.78667 0.36033 0.02953 1.17653 4.955 55.06
        21 SRA 成功餐饮经营者算法(joo) 0.96883 0.63455 0.29217 1.89555 0.94637 0.55506 0.19124 1.69267 0.74923 0.44031 0.12526 1.31480 4.903 54.48
        22 CRO 化学反应优化 0.94629 0.66112 0.29853 1.90593 0.87906 0.58422 0.21146 1.67473 0.75846 0.42646 0.12686 1.31178 4.892 54.36
        23 BIO 血缘遗传优化算法 (JOO) 0.81568 0.65336 0.30877 1.77781 0.89937 0.65319 0.21760 1.77016 0.67846 0.47631 0.13902 1.29378 4.842 53.80
        24 BSA 鸟群算法 0.89306 0.64900 0.26250 1.80455 0.92420 0.71121 0.24939 1.88479 0.69385 0.32615 0.10012 1.12012 4.809 53.44
        25 HS 和声搜索算法 0.86509 0.68782 0.32527 1.87818 0.99999 0.68002 0.09590 1.77592 0.62000 0.42267 0.05458 1.09725 4.751 52.79
        26 SSG 树苗播种和生长算法 0.77839 0.64925 0.39543 1.82308 0.85973 0.62467 0.17429 1.65869 0.64667 0.44133 0.10598 1.19398 4.676 51.95
        27 BCOm 细菌趋化优化算法M 0.75953 0.62268 0.31483 1.69704 0.89378 0.61339 0.22542 1.73259 0.65385 0.42092 0.14435 1.21912 4.649 51.65
        28 ABO 非洲水牛优化 0.83337 0.62247 0.29964 1.75548 0.92170 0.58618 0.19723 1.70511 0.61000 0.43154 0.13225 1.17378 4.634 51.49
        29 (PO)ES (PO) 进化策略 0.79025 0.62647 0.42935 1.84606 0.87616 0.60943 0.19591 1.68151 0.59000 0.37933 0.11322 1.08255 4.610 51.22
        30 FBA 基于分形的算法 0.79000 0.65134 0.28965 1.73099 0.87158 0.56823 0.18877 1.62858 0.61077 0.46062 0.12398 1.19537 4.555 50.61
        31 TSm 禁忌搜索优化算法 0.87795 0.61431 0.29104 1.78330 0.92885 0.51844 0.19054 1.63783 0.61077 0.38215 0.12157 1.11449 4.536 50.40
        32 BSO 头脑风暴优化 0.93736 0.57616 0.29688 1.81041 0.93131 0.55866 0.23537 1.72534 0.55231 0.29077 0.11914 0.96222 4.498 49.98
        33 WOAm 鲸鱼优化算法M 0.84521 0.56298 0.26263 1.67081 0.93100 0.52278 0.16365 1.61743 0.66308 0.41138 0.11357 1.18803 4.476 49.74
        34 AEFA 人工电场算法 0.87700 0.61753 0.25235 1.74688 0.92729 0.72698 0.18064 1.83490 0.66615 0.11631 0.09508 0.87754 4.459 49.55
        35 AEO 基于人工生态系统的优化算法 0.91380 0.46713 0.26470 1.64563 0.90223 0.43705 0.21400 1.55327 0.66154 0.30800 0.28563 1.25517 4.454 49.49
        36 CAm 骆驼算法 M 0.78684 0.56042 0.35133 1.69859 0.82772 0.56041 0.24336 1.63149 0.64846 0.33092 0.13418 1.11356 4.444 49.37
        37 ACOm 蚁群优化 M 0.88190 0.66127 0.30377 1.84693 0.85873 0.58680 0.15051 1.59604 0.59667 0.37333 0.02472 0.99472 4.438 49.31
        38 BFO-GA 细菌觅食优化 - ga 0.89150 0.55111 0.31529 1.75790 0.96982 0.39612 0.06305 1.42899 0.72667 0.27500 0.03525 1.03692 4.224 46.93
        39 SOA 简单优化算法 0.91520 0.46976 0.27089 1.65585 0.89675 0.37401 0.16984 1.44060 0.69538 0.28031 0.10852 1.08422 4.181 46.45
        40 ABHA 人工蜂巢算法 0.84131 0.54227 0.26304 1.64663 0.87858 0.47779 0.17181 1.52818 0.50923 0.33877 0.10397 0.95197 4.127 45.85
        41 ACMO 大气云层模型优化 0.90321 0.48546 0.30403 1.69270 0.80268 0.37857 0.19178 1.37303 0.62308 0.24400 0.10795 0.97503 4.041 44.90
        42 ADAMm 自适应动量评估 M 0.88635 0.44766 0.26613 1.60014 0.84497 0.38493 0.16889 1.39880 0.66154 0.27046 0.10594 1.03794 4.037 44.85
        43 CGO 混沌博弈优化算法 0.57256 0.37158 0.32018 1.26432 0.61176 0.61931 0.62161 1.85267 0.37538 0.21923 0.19028 0.78490 3.902 43.35
        44 CROm 珊瑚礁优化算法 M 0.78512 0.46032 0.25958 1.50502 0.86688 0.35297 0.16267 1.38252 0.63231 0.26738 0.10734 1.00703 3.895 43.27
        45 ATAm 人工部落算法 M 0.71771 0.55304 0.25235 1.52310 0.82491 0.55904 0.20473 1.58867 0.44000 0.18615 0.09411 0.72026 3.832 42.58
        ES 老鹰策略优化 0.70776 0.43078 0.27475 1.41328 0.71737 0.34484 0.15973 1.22194 0.51846 0.27754 0.11063 0.90663 3.542 39.35
        RW 神经类群优化算法2 (joo) 0.48754 0.32159 0.25781 1.06694 0.37554 0.21944 0.15877 0.75375 0.27969 0.14917 0.09847 0.52734 2.348 26.09


        总结

        老鹰策略算法在种群优化基准测试中表现一般,得分率为39%,因此未能进入前 45 名正式榜单,仅作为参考列出。

        尽管如此,该算法仍然值得关注,因为它基于老鹰捕猎行为的生物学模型,提出了独具匠心的两阶段搜索概念。将莱维飞行用于全局探索是一个在数学上相当优雅的方案,已在理论上被证明对于随机搜索问题是最优的。自适应实现机制,包括阶段之间的动态切换和停滞期间的参数自动调整,显示出改进基本概念的潜力。

        该算法可能在特定类别的问题中找到自己的定位 —— 这些问题中全局探索与局部开发之间的平衡至关重要,尤其是在存在多个局部最优和噪声数据的情况下。将萤火虫算法集成用于局部搜索,在两种受自然启发的方法之间创造了有趣的协同效应,尽管整体性能表明还需要进一步的参数优化,并可能需要对底层机制进行修改。

        老鹰策略算法可以被视为优化混合方法的一个有启发性的例子,也是开发结合不同元启发式算法的更先进方法的基础。其相对简单的实现和直观的逻辑,使该算法适合用于进化计算领域的研究实验。

        tab

        图 2. 根据相应测试结果的算法颜色分级

        图表

        图 3. 算法测试结果直方图(比例从 0 到 100,越高越好,其中 100 是最大可能的理论结果,存档中有一个用于计算评级表的脚本)

        ES算法的优缺点:

        优点:

        1. 速度快。

        缺点:

        1. 参数量大。

        本文附带了一个压缩包,其中包含了算法代码的当前版本。作者不对标准算法描述的绝对准确性承担责任。因为为了提升搜索能力,本文已对原算法进行了多处修改。文章中表述的结论和论断是基于实验的结果。


        本文使用的程序文件

        # 名称 类型 说明
        1 #C_AO.mqh

        种群优化算法的父类
        2 #C_AO_enum.mqh

        群体优化算法枚举
        3 TestFunctions.mqh

        测试函数库
        4
        TestStandFunctions.mqh

        测试台函数库
        5
        Utilities.mqh

        辅助函数库
        6
        CalculationTestResults.mqh

        用于计算对比表中结果的脚本
        7
        Testing AOs.mq5
        脚本 所有种群优化算法的统一测试平台
        8
        Simple use of population optimization algorithms.mq5
        脚本
        一个不包含可视化功能的种群优化算法使用示例
        9
        Test_AO_ES.mq5
        脚本 ES测试程序

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

        附加的文件 |
        ES.zip (224.37 KB)
        交易策略 交易策略
        各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
        您应该了解的MQL5向导技巧(第七十一部分):使用MACD与OBV形态 您应该了解的MQL5向导技巧(第七十一部分):使用MACD与OBV形态
        指数平滑异同移动平均线震荡指标(MACD)与能量潮指标(OBV),是另一组可在MQL5智能交易系统(EA)中联合使用的技术指标。与本系列文章一贯的思路相同,这一组合具有互补性:MACD用于确认趋势,OBV则用于验证成交量。我们依旧通过MQL5向导来构建并测试这一组合的潜在效果。
        新手在交易中的10个基本错误 新手在交易中的10个基本错误
        新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
        希尔伯特-施密特独立性判据(HSIC) 希尔伯特-施密特独立性判据(HSIC)
        本文讨论了非参数 HSIC(希尔伯特-施密特独立性判据)统计检验,该检验旨在识别数据中的线性和非线性依赖关系。本文提出了两种用 MQL5 语言计算 HSIC 的算法实现:精确置换测试和伽马近似法。该方法的有效性通过模拟特征与目标变量之间非线性关系的合成数据得到了验证。