极值优化(EO)·进阶篇
📘

极值优化(EO)·进阶篇

第 2/2 篇

◍ 初始化种群时怎么给智能体随机播种

遗传算法跑起来之前,得先把种群里的每个智能体填好初始值。具体做法是:对种群中每一个智能体,再遍历它的各个组件(这里组件就是坐标维度),在各自合法取值区间内均匀随机赋值。比如一个智能体有 3 个坐标分量,每个分量允许范围是 [0, 100],那初始化时就是三次独立 rand() 映射,彼此不相关。 如果问题空间本身是离散的(例如手数档位、整数根周期),在连续随机赋值之后还要做一次数据离散化:把落在连续区间里的浮点值按步长归整。不做这步,后续交叉变异会算出 MT5 报单拒绝的非标准参数。 实操上,种群规模设 50、坐标维度 5 时,初始化会生成 250 个随机数;开 MT5 用 OnInit 里写循环即可验证分布是否覆盖全区间,避免早熟收敛。外汇与贵金属杠杆高,参数优化结果仅代表历史样本拟合,实盘存在显著回撤风险。

智能体逐轮择优与变异的具体走法

这套优化不是盲目撒点,而是对种群里每一个智能体单独走一遍「排序—选靶—拆件—换值」的循环。先给全部智能体算适应度并排好序,劣解排前面,保证后续按概率挑的时候差个体也有被翻盘的机会。 选哪个智能体动手,靠的是幂律分布 P(n) ∝ n^(-τ):位次越靠后(越差)被抽中的概率越高,但头部优解仍保留小概率入选。抽中位次后,该智能体就定为这一轮待优化目标。 接着把目标智能体拆成组件,逐个算组件适应度,公式是 适应度 = 1 -(相对最优值的归一化偏差)。按升序排,偏差大的劣质组件排最前。再用一次幂律分布抽组件位次,给选中的组件重随机赋合法值,做边界校验和离散化。MT5 上跑这套,调 τ 能直接改变「重劣质还是保优质」的搜索倾向。

「EO类的排序与极值更新实现」

极值优化(EO)在 MT5 里落地,核心是把「种群按适应度排好序、只动最差组件」这件事写成可循环的类方法。下面这段 C_AO_EO 的实现,依赖几个硬参数:popSize 决定智能体总数,tau 控制幂律筛选偏重度,eliteUpdate 决定每轮动多少个体,greedyStart 管初始化时贪心占比。 Init 方法先跑 StandardInit 做基础配置,失败就直接退出;成功后才按 coords(变量维数)和 popSize 重分配 compRanks、agentRanks 两个排序数组。注意这里数组容量和维度严格绑定,改了问题维度不重调 Init 会越界。 ApplyExtremalOptimization 是主循环。代码里 numUpdates 本应按 popSize*eliteUpdate 算,但实际 for 循环写死跑满 popSize——这是个可验证的细节,你在 MT5 里跑回测时若发现每轮全量更新而非按比例,根源就在这行。筛选逻辑是:智能体按 f 从差到好冒泡排,再用 SelectRankByPowerLaw 按排名抽目标;抽中后对其组件算单点适应度再排,同样幂律抽一个组件,无条件是 RNDfromCI 随机重赋值。

组件适应度函数把「偏差越小分越高」写得很直白:range>0 时 fitness=1.0 -c-cB/range。cB 是基准值(通常是当前全局最优组件),所以组件越靠近最优解得分越高,最差组件排名垫底、被抽中概率最大。

SelectRankByPowerLaw 用逆变换法生成排名:tau≠1 时走通用归一化公式,tau=1 时单独用 log 公式。返回的 rank 越低(越差)选中概率越高,这是 EO 区别于遗传算法「交叉变异」的关键——它不保护优良基因,只定点炸最差单元。 Revision 每轮把种群按最大化排,a[0].f 优于 fB 就复制组件进 cB、刷新 fB。外汇与贵金属参数优化用这套,过拟合风险高,实盘前务必用 out-of-sample 数据复核。

C++
class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

class=class="str">"cmt">//--- Initialization

class="type">bool C_AO_EO::Init(const class="type">class="kw">double &rangeMinP  [],

                    const class="type">class="kw">double &rangeMaxP  [],

                    const class="type">class="kw">double &rangeStepP [],

                    const class="type">int epochsP = class="num">0)

{

  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;



  class=class="str">"cmt">//------------------------------------------------------------------

  ArrayResize(compRanks, coords);

  ArrayResize(agentRanks, popSize);



  class="kw">return true;

}

class=class="str">"cmt">//————————————————————————————————————————————————————————————————————



class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

class=class="str">"cmt">//--- Main loop of the algorithm

class="type">void C_AO_EO::Moving()

{

  class=class="str">"cmt">// Initial population setup

  if (!revision)

  {

    class="type">int greedyCount = (class="type">int)(popSize * greedyStart);



    for (class="type">int i = class="num">0; i < popSize; i++)

    {

      class=class="str">"cmt">// Random initialization for the rest

      for (class="type">int c = class="num">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]);

      }

    }



    revision = true;

    class="kw">return;

  }



  class=class="str">"cmt">// Apply Extremal Optimization ---------------------------------

  ApplyExtremalOptimization();

}

class=class="str">"cmt">//————————————————————————————————————————————————————————————————————



class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

class=class="str">"cmt">//--- Apply Extremal Optimization

class="type">void C_AO_EO::ApplyExtremalOptimization()

{

  class=class="str">"cmt">// Number of agents to update in this iteration

  class="type">int numUpdates = MathMax(class="num">1, (class="type">int)(popSize * eliteUpdate));



  class=class="str">"cmt">// Update selected agents based on EO principle

  class=class="str">"cmt">//for (class="type">int update = class="num">0; update < numUpdates; update++)

  for (class="type">int update = class="num">0; update < popSize; update++)

  {

    class=class="str">"cmt">// Step class="num">1: Select an agent to modify

    class=class="str">"cmt">// Use ranking by overall fitness

    class="type">int targetAgent;



    class=class="str">"cmt">// Rank agents by fitness(from worst to best for maximization)

    for (class="type">int i = class="num">0; i < popSize; i++)

    {

      agentRanks [i].idx = i;

      agentRanks [i].fitness = a [i].f;

    }



    class=class="str">"cmt">// Sort(worst first to maximize)

    for (class="type">int i = class="num">0; i < popSize - class="num">1; i++)

    {

      for (class="type">int j = i + class="num">1; j < popSize; j++)

      {

        if (agentRanks [i].fitness > agentRanks [j].fitness)

        {

          RankedAgent temp = agentRanks [i];

          agentRanks [i] = agentRanks [j];

          agentRanks [j] = temp;

        }

      }

    }



    class=class="str">"cmt">// Select an agent according to the power-law distribution

    class="type">int rank    = SelectRankByPowerLaw(popSize);

    targetAgent = agentRanks [rank].idx;



    class=class="str">"cmt">// Step class="num">2: Rank the components of the selected agent

    for (class="type">int c = class="num">0; c < coords; c++)

    {

      compRanks [c].agentIdx     = targetAgent;

      compRanks [c].componentIdx = c;

      compRanks [c].fitness      = CalculateComponentFitness(targetAgent, c);

    }



    class=class="str">"cmt">// Sort components(worst first)

    for (class="type">int i = class="num">0; i < coords - class="num">1; i++)

    {

      for (class="type">int j = i + class="num">1; j < coords; j++)

      {

        if (compRanks [i].fitness > compRanks [j].fitness)

        {

          RankedComponent temp = compRanks [i];

          compRanks [i] = compRanks [j];

          compRanks [j] = temp;

        }

      }

    }



    class=class="str">"cmt">// Step class="num">3: Select a component to change according to P(n) ∝ n^(-τ)

    class="type">int compRank = SelectRankByPowerLaw(coords);

    class="type">int compIdx  = compRanks [compRank].componentIdx;



    class=class="str">"cmt">// Step class="num">4: Replace the selected component with a new random value

    class=class="str">"cmt">// This is the key principle of EO - unconditional replacement with random

    a [targetAgent].c [compIdx] = u.RNDfromCI(rangeMin [compIdx], rangeMax [compIdx]);



    class=class="str">"cmt">// Check boundaries

    a [targetAgent].c [compIdx] = u.SeInDiSp(a [targetAgent].c [compIdx],

                                              rangeMin [compIdx],

                                              rangeMax [compIdx],

                                              rangeStep [compIdx]);

  }

}

class=class="str">"cmt">//————————————————————————————————————————————————————————————————————



class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

class=class="str">"cmt">//--- Calculate the fitness component

class="type">class="kw">double C_AO_EO::CalculateComponentFitness(class="type">int agentIdx, class="type">int componentIdx)

{

  class=class="str">"cmt">// For the general optimization problem we use a simple metric

  class=class="str">"cmt">// λi = relative contribution of the component to the fitness



  class="type">class="kw">double fitness = class="num">0.0;



  class="type">class="kw">double range = rangeMax [componentIdx] - rangeMin [componentIdx];

  if (range > class="num">0)

  {

    class=class="str">"cmt">// Normalized deviation

    class="type">class="kw">double deviation = MathAbs(a [agentIdx].c [componentIdx] - cB [componentIdx]) / range;

    fitness =class="num">1.0 - deviation;class=class="str">"cmt">// Invert so that bigger = better

  }



  class="kw">return fitness;

}

class=class="str">"cmt">//————————————————————————————————————————————————————————————————————



class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

class=class="str">"cmt">//--- Select a rank according to a power-law distribution

class="type">int C_AO_EO::SelectRankByPowerLaw(class="type">int maxRank)

{

  class=class="str">"cmt">// P(n) ∝ n^(-τ), where n is a rank from class="num">1 to maxRank

  class=class="str">"cmt">// Use the inverse transformation method



  class="type">class="kw">double r = u.RNDprobab();



  if (tau != class="num">1.0)

  {

    class=class="str">"cmt">// General case: inverse transform for P(n) ∝ n^(-τ)

    class="type">class="kw">double norm = (class="num">1.0 - MathPow(maxRank + class="num">1.0, class="num">1.0 - tau)) / (class="num">1.0 - tau);

    class="type">class="kw">double x = r * norm;

    class="type">int rank = (class="type">int)MathPow((class="num">1.0 - tau) * x + class="num">1.0, class="num">1.0 / (class="num">1.0 - tau)) - class="num">1;



    if (rank >= maxRank) rank = maxRank - class="num">1;

    if (rank < class="num">0) rank = class="num">0;



    class="kw">return rank;

  }

  else

  {

    class=class="str">"cmt">// Special case τ = class="num">1: P(n) ∝ class="num">1/n

    class="type">class="kw">double norm = MathLog(maxRank + class="num">1.0);

    class="type">int rank = (class="type">int)(MathExp(r * norm) - class="num">1.0);



    if (rank >= maxRank) rank = maxRank - class="num">1;

    if (rank < class="num">0) rank = class="num">0;


    class="kw">return rank;

  }

}

class=class="str">"cmt">//————————————————————————————————————————————————————————————————————



class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

class=class="str">"cmt">//--- Update the best solutions

class="type">void C_AO_EO::Revision()

{

  class=class="str">"cmt">// Sort the population for MAXIMIZATION

  class="kw">static S_AO_Agent aT [];

  ArrayResize(aT, popSize);



  class=class="str">"cmt">// Use the built-in sorting function

  u.Sorting(a, aT, popSize);



  class=class="str">"cmt">// Update the global best solution

  if (a [class="num">0].f > fB)

  {

    ArrayCopy(cB, a [class="num">0].c, class="num">0, class="num">0, WHOLE_ARRAY);

    fB = a [class="num">0].f;

  }

}

class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

◍ 改良极值优化跑分与代码骨架

基准版 EO(Boettcher-Percus)在三个测试函数上各跑 10000 次:Hilly's 在 5/25/500 维得分 0.515 / 0.291 / 0.252,Forest's 为 0.367 / 0.195 / 0.154,Megacity's 为 0.258 / 0.127 / 0.094,总分 2.25271,折算覆盖率 25.03%。 把最差解『复活』机制加进去、变异改用幂律分布选 donor 后,同样 10000 次运行:Hilly's 升到 0.762 / 0.772 / 0.317,Forest's 到 0.99999 / 0.768 / 0.235,Megacity's 到 0.748 / 0.540 / 0.142,总分 5.28422(58.71%)。低维函数上离散度偏大,但总分接近翻了一倍多,说明最差解重采样确实拉了种群多样性。外汇与贵金属参数寻优属高风险场景,回测分高不代表实盘概率占优,需自测。 代码里 C_AO_EOm 继承自 C_AO,构造函数把 popSize=50、popRaising=3、mutationRate=0.1、powCh=2.0、powMut=8.0 写死进 params 数组,方便后面 SetParams 动态改。Init 只做标准初始化加迭代计数;Moving 首轮铺随机种群,之后每轮用 pow(rnd,powCh) 挑父代,mutType<mutationRate 走 PowerDistribution 变异,否则朝 cB 加噪定向移;Revision 排序后更新全局最优,并对末尾 popRaising 个最差个体重赋 fW~fB 间随机适应度。 [CODE] 内 C_AO_EOm 类逐行拆解: class C_AO_EOm : public C_AO —— 继承基础优化类 { 公开段:析构为空;构造函数填 ao_name="EOm"、默认五参数并 ArrayResize(params,5) 存名值对 SetParams 把 params 值写回 popSize/popRaising/mutationRate/powCh/powMut Init 调 StandardInit,失败返 false,成功置 currentEpoch=0、totalEpochs=epochsP 返 true Moving 首轮 if(!revision) 双层循环 RNDfromCI 生成初解并 SeInDiSp 对齐步长,置 revision=true 后 return 后续轮:aT 暂存,pow(rnd,powCh) 算 ind,mutType<mutationRate 用 PowerDistribution 否则 cB 定向噪声,SeInDiSp 收口,ArrayCopy 回写 a Revision:Sorting 排序,a[0].f>fB 则更新 cB/fB,取末尾 fW,循环 popRaising 次把最差 f 重赋 RNDfromCI(fW,fB),再排序 } 想把这套接进自己的 EA 调参,先把 popMut=8.0 降到 4.0 试一轮,看 Megacity's 500 维是否还能守住 0.14 以上。

C++
class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

class C_AO_EOm : class="kw">public C_AO

{

  class="kw">public: class=class="str">"cmt">//----------------------------------------------------------

  ~C_AO_EOm() { }

  C_AO_EOm()

  {

    ao_name = "EOm";

    ao_desc = "Extremal Optimization M";
    ao_link = "[MQL5官方文档]



    popSize        = class="num">50;      class=class="str">"cmt">// Population size

    popRaising     = class="num">3;       class=class="str">"cmt">// Boost the worst agents

    mutationRate   = class="num">0.1;     class=class="str">"cmt">// Mutation probability

    powCh          = class="num">2.0;     class=class="str">"cmt">// Selection power-law exponent

    powMut         = class="num">8.0;     class=class="str">"cmt">// Mutation power-law exponent 



    ArrayResize(params, class="num">5);



    params [class="num">0].name = "popSize";        params [class="num">0].val = popSize;
    params [class="num">1].name = "popRaising";     params [class="num">1].val = popRaising;
    params [class="num">2].name = "mutationRate";   params [class="num">2].val = mutationRate;
    params [class="num">3].name = "powCh";          params [class="num">3].val = powCh;
    params [class="num">4].name = "powMut";         params [class="num">4].val = powMut;
  }



  class="type">void SetParams()
  {
    popSize        = (class="type">int)params [class="num">0].val;
    popRaising     = (class="type">int)params [class="num">1].val;
    mutationRate   = params      [class="num">2].val;
    powCh          = params      [class="num">3].val;
    powMut         = params      [class="num">4].val;
  }



  class="type">bool Init(const class="type">class="kw">double &rangeMinP  [],
             const class="type">class="kw">double &rangeMaxP  [],
             const class="type">class="kw">double &rangeStepP [],
             const class="type">int     epochsP = class="num">0);

  class="type">void Moving();
  class="type">void Revision();



  class=class="str">"cmt">//------------------------------------------------------------------
  class="type">int    popRaising;       class=class="str">"cmt">// Boost the worst agents
  class="type">class="kw">double mutationRate;     class=class="str">"cmt">// Mutation probability
  class="type">class="kw">double powCh;            class=class="str">"cmt">// Selection power-law exponent
  class="type">class="kw">double powMut;           class=class="str">"cmt">// Mutation power-law exponent



  class="kw">private: class=class="str">"cmt">//---------------------------------------------------------
  class="type">int    currentEpoch;     class=class="str">"cmt">// current epoch
  class="type">int    totalEpochs;      class=class="str">"cmt">// total number of epochs



  class="type">void MutateComponent(class="type">int agentIdx, class="type">int componentIdx);
};
class=class="str">"cmt">//————————————————————————————————————————————————————————————————————



class=class="str">"cmt">//————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Initialization
class="type">bool C_AO_EOm::Init(const class="type">class="kw">double &rangeMinP  [],
                     const class="type">class="kw">double &rangeMaxP  [],
                     const class="type">class="kw">double &rangeStepP [],
                     const class="type">int epochsP = class="num">0)
{
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;

  class=class="str">"cmt">//------------------------------------------------------------------
  currentEpoch = class="num">0;
  totalEpochs  = epochsP;

  class="kw">return true;
}
class=class="str">"cmt">//————————————————————————————————————————————————————————————————————



class=class="str">"cmt">//————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Main loop of the algorithm
class="type">void C_AO_EOm::Moving()
{
  currentEpoch++;

  class=class="str">"cmt">// Initial population setup
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">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]);
      }
    }

    revision = true;
    class="kw">return;
  }

  class=class="str">"cmt">//Apply Extremal Optimization---------------------------------------
  class="kw">static S_AO_Agent aT []; ArrayResize(aT, popSize);

  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    aT [i].Init(coords);

    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class="type">class="kw">double rnd = u.RNDprobab(); rnd = pow(rnd, powCh);
      class="type">int ind = (class="type">int)u.Scale(rnd, class="num">0.0, class="num">1.0, class="num">0, popSize - class="num">1);

      class=class="str">"cmt">// Select the mutation type
      class="type">class="kw">double mutType = u.RNDprobab();

      if (mutType < mutationRate)
      {
        aT [i].c [c] = u.PowerDistribution(a [ind].c [c], rangeMin [c], rangeMax [c], powMut);
      }
      else
      {
        class=class="str">"cmt">// Directed movement towards the better with noise
        aT [i].c [c] = a [ind].c [c] + u.RNDprobab() * (cB [c] - a [ind].c [c]);
      }

      class=class="str">"cmt">// Check boundaries
      aT [i].c [c] = u.SeInDiSp(aT [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }

  for (class="type">int i = class="num">0; i < popSize; i++) ArrayCopy(a [i].c, aT [i].c);
}
class=class="str">"cmt">//————————————————————————————————————————————————————————————————————



class=class="str">"cmt">//————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Update the best solutions
class="type">void C_AO_EOm::Revision()
{
  class=class="str">"cmt">// Sort the population  --------------------------------------------
  class="kw">static S_AO_Agent aT []; ArrayResize(aT, popSize);
  u.Sorting(a, aT, popSize);

  class=class="str">"cmt">// Update the global best solution
  if (a [class="num">0].f > fB)
  {
    ArrayCopy(cB, a [class="num">0].c, class="num">0, class="num">0, WHOLE_ARRAY);
    fB = a [class="num">0].f;
  }

  fW = a [popSize - class="num">1].f;

  class=class="str">"cmt">//------------------------------------------------------------------
  for (class="type">int i = class="num">0; i < popRaising; i++)
  {
    a [popSize - class="num">1 - i].f = u.RNDfromCI(fW, fB);
  }

  u.Sorting(a, aT, popSize);
}
class=class="str">"cmt">//————————————————————————————————————————————————————————————————————

a [popSize - class="num">1 - i].f = u.RNDfromCI(fW, fB);

EOm 在三大测试函数上的真实排位

极值优化算法(EOm)跑完 Hilly、Forest、Megacity 三组测试函数后,在本次 45 个算法 + 随机游走的横向排名里落到了第 12 位,综合得分 5.284,占榜首 ANS(6.134)的 58.71%。 拆开看单函数表现:Hilly 上 EOm 的 10p/50p/1000p 分别是 0.76166 / 0.77242 / 0.31747,最终 1.85155;Forest 上三项为 0.99999 / 0.76751 / 0.23527,最终 2.00277;Megacity 离散场景下是 0.74769 / 0.53969 / 0.14249,最终 1.42987。它的 Forest 10p 几乎拉满,但 Hilly 与 Megacity 的尾部收敛明显弱于前 11 名。 对照榜首 ANS 的 Megacity 最终 0.63477,EOm 只有 0.53969,差距约 0.095。若你在 MT5 里用 EA 做参数寻优,EOm 这类局部翻山能力中等的算法,可能更适合维度不高、约束松散的品种,外汇与贵金属波动无序,直接套用有过高拟合的高风险。

「别急着下结论」

EOm 在 45 个种群智能优化算法里排第 12,本质只是保留了原始 EO 幂律分布内核的混合改法,并非对经典理论的复刻。最差智能体复活机制把早熟收敛的概率压下去了,简化结构又让完整解直接迭代,收敛速度倾向更快。 它适合既要解质量又要控算力的场景,但在低维平滑函数上只是平均水平,离散度也偏大——这套权衡得自己跑压缩包里的脚本验。外汇与贵金属市场的高风险环境下,任何优化器都只是工具,别当圣杯。

常见问题

用均匀分布或带扰动的随机函数在参数区间内撒点,避免集中初始化;可加一轮去重判断减少重叠个体。
每轮只替换最差个体,变异步长随迭代衰减;保留历史极值做对照,防止陷入局部最优。
小布可自动按你给的区间播种种群、逐轮记录极值并标出早熟风险,省去手写循环和日志。
EOm 在球面与 Rosenbrock 上居中、在 Rastrigin 上偏后;排位受维度和种子影响,仅作参考。
会出现极值不刷新或最优个体被误删;表现为收敛曲线走平或突然跳变,需核对比较逻辑。