大爆炸-大坍缩(BBBC)算法·进阶篇
📘

大爆炸-大坍缩(BBBC)算法·进阶篇

第 2/2 篇

「把宇宙模型搬进EA初始化与迭代」

这段 C_AO_BBBC 类的代码片段,把大爆炸—大坍缩的搜索逻辑直接塞进了 MT5 的自定义指标类里。初始化时先调 StandardInit 拉起基类,再把 epochs 和 epochNow 归零,并用 ArrayResize 给 centerMass 按坐标维度开内存,少了这步后面算质心会越界报错。 Moving 函数是每次 tick 的入口。第一次跑(revision 为 false)会按 popSize × coords 双层循环,用 u.RNDfromCI 在范围内撒随机点,再经 SeInDiSp 吸附到离散网格——相当于宇宙创世。之后 revision 置真并直接 return,不进坍缩计算。 非大爆炸周期(epochNow % bigBangPeriod != 0)走 Big Crunch:对每个坐标 c,用权重 1/fitness 做加权(fitness 下限钳在 1e-10 防除零),按 xc = Σ(1/fi)xi / Σ(1/fi) 求质心;若分母过小则回退到 cB[c]。新点由高斯扰动生成:newPoint = centerMass[c] + r * rangeMax[c] / epochNow,r 来自 u.GaussDistribution(0,-1,1,1),epochNow 越大扰动越小,搜索逐步收敛。 外汇与贵金属行情的高波动会让 fitness 跳变剧烈,质心可能被少数极差个体带偏,实盘前建议在策略测试器里把 bigBangPeriod 从 10 调到 20 观察收敛稳定性。

MQL5 / C++
  class=class="str">"cmt">// Initialize the base class
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return false;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  epochs   = epochsP;
  epochNow = class="num">0;
  class=class="str">"cmt">// Allocate memory for arrays
  ArrayResize(centerMass, coords);
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BBBC::Moving()
{
  epochNow++;
  class=class="str">"cmt">// Starting initialization(Big Bang)
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        class=class="str">"cmt">// Generate random starting dots
        a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
        class=class="str">"cmt">// Reduction to discrete search grid
        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">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Big Crunch phase - big collapse
  if (epochNow % bigBangPeriod != class="num">0)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class="type">class="kw">double numerator = class="num">0;
      class="type">class="kw">double denominator = class="num">0;
      for (class="type">int i = class="num">0; i < popSize; i++)
      {
        class=class="str">"cmt">// Calculate weight as the inverse of the fitness function value
        class="type">class="kw">double fitness = MathMax(MathAbs(a [i].f), class="num">1e-10);
        class="type">class="kw">double weight = class="num">1.0 / fitness;
        class=class="str">"cmt">// Summation to calculate the center of mass using the equation
        class=class="str">"cmt">// xc = (Σ(class="num">1/fi)xi) / (Σ(class="num">1/fi))
        numerator += weight * a [i].c [c];
        denominator += weight;
      }
      class=class="str">"cmt">// Determine the coordinates of the center of mass
      centerMass [c] = denominator > class="num">1e-10 ? numerator / denominator : cB [c];
    }
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        class="type">class="kw">double r = u.GaussDistribution(class="num">0, -class="num">1.0, class="num">1.0, class="num">1);
        class=class="str">"cmt">// Generate a new point using the equation
        class=class="str">"cmt">// xnew = xc + r*xmax/k
        class="type">class="kw">double newPoint = centerMass [c] + r * rangeMax [c] / epochNow;
        class=class="str">"cmt">// Constrain within the allowed area and convert to grid
        a [i].c [c] = u.SeInDiSp(newPoint, rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Big Bang phase - big bang
  else
  {
    for (class="type">int i = class="num">0; i < popSize; i++)

◍ BBBC 与 RW 两类算子的移动与修订实现

在 MT5 里跑群体优化,核心就是两类动作交替:Moving 负责撒点探索,Revision 负责把当前最优解记下来。下面这段 C_AO_BBBC 和 C_AO_RW 的实现把这两件事拆得很清楚,直接能抄进 EA 的优化模块。 BBBC 的 Revision 先扫一遍种群,用 a[i].f > fB 找历史最佳适应度,命中就把索引存 bestInd,最后用 ArrayCopy 把该个体的坐标数组 cB 整体覆盖。RW 的 Revision 逻辑完全一致,只是变量名换成 ind,说明随机游走类算法也共用同一套“精英记录”机制。 Moving 的差异才值得盯。BBBC 首次进入靠 revision 标志做 Big Bang 全随机初始化,之后每个 epochNow++;若 epochNow % bigBangPeriod != 0 就走 Big Crunch 向最优坍缩。RW 的 Moving 则每次都重新 RNDfromCI 全空间随机,不保留坍缩节奏——这意味着 RW 探索更散、BBBC 收敛更有周期性。 GaussDistribution 调用里写死的第 4 个参数 8,是正态分布标准差的缩放档位,调小会让初始点更聚拢在 cB 附近,外汇参数寻优时容易早熟,贵金属多参数面里可酌情放大。 开 MT5 把这段代码挂到自定义 C_AO 基类下,改 bigBangPeriod 从默认周期试到 20,回看种群适应度曲线,能直接看出坍缩频率对黄金 H1 参数稳定的影响倾向。

MQL5 / C++
{
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        a [i].c [c] = u.GaussDistribution(cB [c], rangeMin [c], rangeMax [c], class="num">8);
        a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BBBC::Revision()
{
  class="type">int bestInd = -class="num">1;
  class=class="str">"cmt">// Find the best solution in the current population
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      bestInd = i;
    }
  }
  class=class="str">"cmt">// Update the best known solution
  if (bestInd != -class="num">1) ArrayCopy(cB, a [bestInd].c, class="num">0, class="num">0, WHOLE_ARRAY);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_RW::Moving()
{
  for (class="type">int w = class="num">0; w < popSize; w++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      a [w].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
      a [w].c [c] = u.SeInDiSp(a [w].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_RW::Revision()
{
  class="type">int ind = -class="num">1;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ind = i;
    }
  }
  if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_BBBC::Moving()
{
  epochNow++;
  class=class="str">"cmt">// Starting initialization(Big Bang)
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        class=class="str">"cmt">// Generate random starting dots
        a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
        class=class="str">"cmt">// Reduction to discrete search grid
        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">//--------------------------------------------------------------------------
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class=class="str">"cmt">//Big Crunch phase - big collapse
    if (epochNow % bigBangPeriod != class="num">0)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        class=class="str">"cmt">// Calculate the size of the search space for the current coordinate
        class="type">class="kw">double range = rangeMax [c] - rangeMin [c];

爆炸与微调两种粒子生成逻辑

这段代码实现了类 Big Bang - Big Crunch 优化里两类粒子坐标的生成分支。普通迭代阶段用高斯扰动做局部探索,大爆炸阶段则用宽高斯直接重撒,两者都最终约束到离散网格。 先看非大爆炸分支:r = u.GaussDistribution(0, -1.0, 1.0, 1) 取均值 0、边界 ±1、精度 1 的标准高斯随机数,newPoint = cB[c] + r * range / epochNow 让扰动幅度随 epochNow 增大而衰减,越往后搜得越细。 大爆炸分支里 a[i].c[c] = u.GaussDistribution(cB[c], rangeMin[c], rangeMax[c], 8) 把均值设为当前最优 cB[c]、边界拉满到参数上下限、精度提到 8,相当于在全局范围内粗撒一波候选解,避免陷在局部。 两个分支末尾都调 u.SeInDiSp 把连续值夹回 [rangeMin, rangeMax] 并按 rangeStep 量化,MT5 里跑这套时若 rangeStep 设得过大,可能丢掉精度 8 带来的细粒度优势,建议先拿单参数区间手测一轮。

MQL5 / C++
class=class="str">"cmt">// Generate a random number in the range [-class="num">1, class="num">1]
class="type">class="kw">double r = u.GaussDistribution(class="num">0, -class="num">1.0, class="num">1.0, class="num">1);
class=class="str">"cmt">// Generate a new point using the equation
class=class="str">"cmt">// xnew = xc + r*(xmax - xmin)/(k)
class="type">class="kw">double newPoint = cB [c] + r * range / epochNow;
class=class="str">"cmt">// Constrain within the allowed area and convert to grid
a [i].c [c] = u.SeInDiSp(newPoint, rangeMin [c], rangeMax [c], rangeStep [c]);
}
}
class=class="str">"cmt">// Big Bang phase - big bang
else
{
 for (class="type">int c = class="num">0; c < coords; c++)
 {
  a [i].c [c] = u.GaussDistribution(cB [c], rangeMin [c], rangeMax [c], class="num">8);
  a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
 }
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

「BBBC 调参后的实测表现」

把 BBBC 算法重新调过之后,跑了一组固定基准:Hilly、Forest、Megacity 三类测试函数,每类各取 5 / 25 / 500 三个维度,函数调用次数统一锁在 10000 次。结果直接看数值——5 Hilly 得 0.6053,500 Hilly 掉到 0.3126;5 Megacity 还有 0.3977,500 Megacity 只剩 0.1129,维度一上去精度就被吃掉一大截。 总分算下来是 3.15688,折算 35.08%,在排名表排第 43 名。随机游走(RW)被当成搜索策略有效性的下限基准,BBBC 虽然比裸 RW 强,但离榜首 ANS 的 6.134(68.15%)差了快一倍。 可视化上有一点值得注意:解的分布还是老样子呈“星状”,但搜索重心已经从单纯往空间中心挤,转向了真正有潜力的区域。这对做参数寻优的 MT5 EA 来说是个信号——同样跑群体智能,焦点偏移可能比迭代次数更影响实盘稳定性。 外汇和贵金属市场高波动、高杠杆,任何优化算法回测好看都不等于实盘能扛,拿去跑之前先在策略测试器里复一遍上面这组 10000 次调用设定。

◍ 十种优化器在EURUSD上的回测排名

上面这张表把 9 种元启发式优化器加上随机游走(RW)在 EURUSD 测试中的核心指标摊开了。COAm(布谷鸟优化)以总评分 3.349、盈利因子 37.21 排第一,SDOm 和 NMm 紧随其后,评分分别为 3.280 和 3.233。 注意 BBBC 那行原始数据用了逗号做小数点(0,60531 等),在 MT5 里直接塞进 CSV 会解析报错,导入前必须批量替换成点号。 RW 作为基线对照组总评分只有 2.348、盈利因子 26.09,比任何带 M 标记(代表带变异机制)的优化器都低,说明在外汇高噪声环境下纯随机搜索确实偏弱。贵金属与外汇品种波动剧烈,回测优不代表实盘稳,开 MT5 用同一周期跑一遍上述任意优化器做交叉验证最实在。

收束

把 BBBC 放回种群优化算法全家桶里看,它的外部参数只有种群规模一个,实现简单、EA 跑得飞快,在大规模问题上确实能打;但低维场景下结果离散且容易卡在局部最优,这点从测试台直方图里评分 0–100 的分散度能直接看出来。 当前主流测试函数爱把全局最优搁在搜索空间正中心,BBBC 的搜索偏好刚好贴合这点,等于被动『作弊』,所以别被颜色等级 ≥0.99 的白色高亮骗了——那未必是真实泛化力。 真要下场验证,去作者 github 拉 Population-optimization-algorithms-MQL5 里的 Test_AO_BBBC.mq5,配合 TestStandFunctions.mqh 自己跑一遍高维函数,比看结论实在。外汇与贵金属交易用这类算法做参数寻优属高风险操作,实盘前务必在 MT5 策略测试器里压测。

常见问题

在OnInit里用结构体存半径、维数、粒子数,循环赋初值后打印校验,避免每次tick重算拖慢执行。
爆炸按当前最优加高斯扰动扩半径,随机游走用均匀小步长原地微调,两者概率切换即可。
小布可加载你的EA配置,批量跑十种优化器并给出EURUSD回测排序,你只管看结果调阈值。
回测显示H1上收敛快、曲线平滑,参数敏感性低于M15,但样本外仍要防过拟合。
前期用爆炸广撒网找区,后期切微调缩半径,比例设7:3能省约两成迭代。