混沌优化算法(COA):续篇·进阶篇
🧬

混沌优化算法(COA):续篇·进阶篇

(2/3)·从 ApplyMutation 到 UpdateSigma,拆解 COA 避免局部最优的底层代码逻辑

含代码示例 第 2/3 篇
很多交易者把优化算法当黑箱,直接套用默认参数跑回测,结果在窄区间里反复打转。忽略变异机制与惩罚动态调整,往往让种群过早收敛,错过更广的解空间。

◍ 收敛判定与停滞重置的实现细节

混沌优化代理里,历史最优值用长度 10 的环形缓冲保存,UpdateBestHistory 每次写入后 historyIndex 按 (historyIndex+1)%10 推进,避免数组越界同时保留最近 10 次记录。写前先用 MathIsValidNumber 拦截非数,防止 NaN 污染后续收敛判断。 IsConverged 扫描这 10 个槽,跳过未初始化(-DBL_MAX)或非法值,累计有效值、极值与和。有效样本不足 5 个、或极大极小相等时直接返回未收敛,样本门槛设 5 而非 10,是为让算法在预热阶段不误报。 相对收敛阈值定为 relDiff < 0.001,即历史最优波动小于均值的 0.1% 才认为收敛;若均值本身接近零,则退化为绝对差小于 eps*10.0。这个 0.1% 是硬编码经验值,黄金小时图调参时可放宽到 0.005 观察早熟概率。 ResetStagnatingAgents 给每个代理维护 stagnationCounter:当本次适应度 f 未超过历史最好 fB 就加一,否则清零。计数器超过 5 才进入重置候选,重置概率 resetProb = 0.2*(1+counter/10),代际越久越容易被突变救活,但单次最多触发约三成个体,保留种群多样性。外汇与贵金属波动剧烈,这类重置逻辑可能显著改变回测曲线,实盘前务必在 MT5 策略测试器用历史数据验证。

MQL5 / C++
class="type">class="kw">double violation = CalculateConstraintValue(agentIdx, c);
if (violation > eps)
{
  class="kw">return false;
}
}
class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_COA_chaos::UpdateBestHistory(class="type">class="kw">double newBest)
{
  class=class="str">"cmt">// Protection against invalid values
  if (!MathIsValidNumber(newBest)) class="kw">return;
  class=class="str">"cmt">// Update the history of the best values
  globalBestHistory [historyIndex] = newBest;
  historyIndex = (historyIndex + class="num">1) % class="num">10;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_COA_chaos::IsConverged()
{
  class=class="str">"cmt">// Check if there is enough data in the history
  class="type">int validValues = class="num">0;
  class="type">class="kw">double sum      = class="num">0.0;
  class="type">class="kw">double minVal   = DBL_MAX;
  class="type">class="kw">double maxVal   = -DBL_MAX;
  class=class="str">"cmt">// Find min, max, and sum of values in history
  for (class="type">int i = class="num">0; i < class="num">10; i++)
  {
    if (globalBestHistory [i] == -DBL_MAX || !MathIsValidNumber(globalBestHistory [i])) class="kw">continue;
    minVal = MathMin(minVal, globalBestHistory [i]);
    maxVal = MathMax(maxVal, globalBestHistory [i]);
    sum += globalBestHistory [i];
    validValues++;
  }
  class=class="str">"cmt">// If there is not enough data or all values are the same
  if (validValues < class="num">5 || minVal == maxVal) class="kw">return false;
  class=class="str">"cmt">// Calculate the average value
  class="type">class="kw">double average = sum / validValues;
  class=class="str">"cmt">// Check the case when the mean is close to zero
  if (MathAbs(average) < eps) class="kw">return MathAbs(maxVal - minVal) < eps * class="num">10.0;
  class=class="str">"cmt">// Relative difference for convergence testing
  class="type">class="kw">double relDiff = MathAbs(maxVal - minVal) / MathAbs(average);
  class="kw">return relDiff < class="num">0.001; class=class="str">"cmt">// Convergence threshold - class="num">0.1%
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_COA_chaos::ResetStagnatingAgents()
{
  class="type">int resetCount = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class=class="str">"cmt">// Increase the stagnation counter if there is no improvement
    if (a [i].f <= a [i].fB)
    {
      agent [i].stagnationCounter++;
    }
    else
    {
      agent [i].stagnationCounter = class="num">0;
    }
    class=class="str">"cmt">// Reset the agent if it has been stagnating for too class="type">long
    if (agent [i].stagnationCounter > class="num">5)
    {
      class=class="str">"cmt">// Reset only some of the agents with a probability depending on stagnation
      class="type">class="kw">double resetProb = class="num">0.2 * (class="num">1.0 + (class="type">class="kw">double)agent [i].stagnationCounter / class="num">10.0);
      if (u.RNDprobab() < resetProb)
      {
        ApplyMutation(i);
        agent [i].stagnationCounter = class="num">0;

「约束违反与惩罚项的梯度计算逻辑」

在混沌优化代理类里,约束处理靠三个底层函数接力:先算单维越界量,再算越界梯度方向,最后把越界平方汇总成惩罚项。 CalculateConstraintValue 对第 coordIdx 维取代理坐标 x,对照 rangeMin/rangeMax:低于下界则 violation 累加 min-x,高于上界则累加 x-max,界内返回 0.0。这里用的是硬线性越界,没有平滑过渡,越界多少记多少。 CalculateConstraintGradient 返回方向信号:x<min 给 -1.0(需增大),x>max 给 1.0(需减小),界内 0.0。CalculateWeightedGradient 则先扫全部维取 maxViolation(下限 eps),若当前维 violation<=eps 直接返 0;否则用 violation/maxViolation 做权重乘梯度,越界占比越大推动越强。 CalculatePenaltyFunction 把各维 violation 平方求和(violation>eps 才计),再乘动态系数 currentSigma 得 penalty,目标函数按最大化处理为 f-penalty。外汇与贵金属参数寻优用这套,模型可能把极端杠杆解罚到失效,实盘高风险,建议开 MT5 把 eps 调到 1e-6 看代理收敛是否变慢。

MQL5 / C++
class="type">class="kw">double C_AO_COA_chaos::CalculateWeightedGradient(class="type">int agentIdx, class="type">int coordIdx)
{
  class=class="str">"cmt">// Calculate the maximum value of the constraint violation
  class="type">class="kw">double maxViolation = eps;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    class="type">class="kw">double violation = CalculateConstraintValue(agentIdx, c);
    maxViolation = MathMax(maxViolation, violation);
  }
  class=class="str">"cmt">// Violation for the current coordinate
  class="type">class="kw">double violation = CalculateConstraintValue(agentIdx, coordIdx);
  class=class="str">"cmt">// If there is no significant violation, class="kw">return class="num">0
  if (violation <= eps) class="kw">return class="num">0.0;
  class=class="str">"cmt">// Calculate the gradient
  class="type">class="kw">double gradient = CalculateConstraintGradient(agentIdx, coordIdx);
  class=class="str">"cmt">// Normalize the impact depending on the degree of violation
  class="type">class="kw">double weight = violation / maxViolation;
  class="kw">return gradient * weight;
}

class="type">class="kw">double C_AO_COA_chaos::CalculateConstraintValue(class="type">int agentIdx, class="type">int coordIdx)
{
  class="type">class="kw">double x = a [agentIdx].c [coordIdx];
  class="type">class="kw">double min = rangeMin [coordIdx];
  class="type">class="kw">double max = rangeMax [coordIdx];
  class=class="str">"cmt">// Smoothed constraint violation function with a smooth transition at the boundary
  class="type">class="kw">double violation = class="num">0.0;
  class=class="str">"cmt">// Check the lower bound
  if (x < min)
  {
    violation += min - x;
  }
  class=class="str">"cmt">// Check the upper bound
  else
    if (x > max)
    {
      violation += x - max;
    }
  class="kw">return violation;
}

class="type">class="kw">double C_AO_COA_chaos::CalculateConstraintGradient(class="type">int agentIdx, class="type">int coordIdx)
{
  class="type">class="kw">double x = a [agentIdx].c [coordIdx];
  class="type">class="kw">double min = rangeMin [coordIdx];
  class="type">class="kw">double max = rangeMax [coordIdx];
  class=class="str">"cmt">// Smoothed gradient for better stability
  if (x < min) class="kw">return -class="num">1.0; class=class="str">"cmt">// Need to increase the value
  else
    if (x > max) class="kw">return class="num">1.0;  class=class="str">"cmt">// Need to decrease the value
  class="kw">return class="num">0.0;
}

class="type">class="kw">double C_AO_COA_chaos::CalculatePenaltyFunction(class="type">int agentIdx)
{
  class=class="str">"cmt">// Base value of the objective function
  class="type">class="kw">double baseValue = a [agentIdx].f;
  class=class="str">"cmt">// Penalty term
  class="type">class="kw">double penaltySum = class="num">0.0;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    class="type">class="kw">double violation = CalculateConstraintValue(agentIdx, c);
    class=class="str">"cmt">// Quadratic penalty for better resolution of solutions close to the boundary
    if (violation > eps)
    {
      penaltySum += violation * violation;
    }
  }
  class=class="str">"cmt">// Apply a dynamic penalty ratio
  class="type">class="kw">double penalty = currentSigma * penaltySum;
  class=class="str">"cmt">// For the maximization problem: f_penalty = f - penalty

混沌优化里的代际推进与参数自适应

这段逻辑跑在 MT5 的 EA 或指标类的逐根 K 线驱动里,核心是一个叫 Moving 的步进函数。它先自增 epochNow 纪元计数,首帧做种群初始化并置 revision 标记后直接返回,后续每帧才进入真正的搜索循环。 每 5 个纪元(epochNow % 5 == 0)触发一次 ResetStagnatingAgents,把长时间不进化的个体重置,避免种群早熟收敛。搜索阶段按纪元切分:epochNow <= S1 走第一载波搜索,<= S1+S2 走第二载波,相当于先广域后局部的两次收敛波。 Revision 函数负责评估与更新。它遍历 popSize 个解,用 CalculatePenaltyFunction 算适应度,遇非有限数直接跳过;个人最优与全局最优分开更新,全局更新时把提升量写进 bestImprovement 并追加到最优值历史。 参数自适应看 successRate:用 improvementCount 除以 2*popSize(至少为1防零除)。当 epochNow<=S1 的全局搜索期,若成功率低于 0.1 就把 alpha[c] 乘 t3 放大搜索半径;低于 0.3 则进入另一档收缩逻辑。把 t3 设为 1.15 左右,能在 EURUSD 15M 回测中让早期探索覆盖更宽参数空间,但外汇与贵金属属高风险品种,参数放大也可能拉长无效计算。

MQL5 / C++
class="type">void C_AO_COA_chaos::Moving()
{
  epochNow++;
  if (!revision)
  {
    InitialPopulation();
    revision = true;
    class="kw">return;
  }
  class=class="str">"cmt">// Dynamically update the penalty parameter
  UpdateSigma();
  class=class="str">"cmt">// Reset stagnating agents
  if (epochNow % class="num">5 == class="num">0)
  {
    ResetStagnatingAgents();
  }
  class=class="str">"cmt">// Determine the search phase
  if (epochNow <= S1)
  {
    FirstCarrierWaveSearch();
  }
  else
    if (epochNow <= S1 + S2)
    {
      SecondCarrierWaveSearch();
    }
}

class="type">void C_AO_COA_chaos::Revision()
{
  class="type">int     improvementCount = class="num">0;
  class="type">class="kw">double  bestImprovement  = class="num">0.0;
  class=class="str">"cmt">// Evaluate all solutions
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class="type">class="kw">double newValue = CalculatePenaltyFunction(i);
    class=class="str">"cmt">// Check the validity of the new value
    if (!MathIsValidNumber(newValue)) class="kw">continue;
    class=class="str">"cmt">// Save the current value for the next iteration
    a [i].f = newValue;
    class=class="str">"cmt">// Update the personal best solution(before a global one for efficiency)
    if (newValue > a [i].fB)
    {
      a [i].fB = newValue;
      ArrayCopy(a [i].cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
      improvementCount++;
    }
    class=class="str">"cmt">// Update the global best solution
    if (newValue > fB)
    {
      class="type">class="kw">double improvement = newValue - fB;
      fB = newValue;
      ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
      class=class="str">"cmt">// Update the history of the best values
      UpdateBestHistory(fB);
      bestImprovement = MathMax(bestImprovement, improvement);
      improvementCount++;
    }
  }
  class=class="str">"cmt">// Adapt search parameters depending on the phase and success
  if (epochNow > class="num">1)
  {
    class=class="str">"cmt">// Search success rate(prevention of zero divide)
    class="type">class="kw">double successRate = (class="type">class="kw">double)improvementCount / MathMax(class="num">1, class="num">2 * popSize);
    class=class="str">"cmt">// Adaptation of the alpha parameter
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class="type">class="kw">double oldAlpha = alpha [c];
      if (epochNow <= S1)
      {
        class=class="str">"cmt">// In the global search phase
        if (successRate < class="num">0.1)
        {
          class=class="str">"cmt">// Very few improvements - increasing the search scope
          alpha [c] *= t3;
        }
        else
          if (successRate < class="num">0.3)
          {

◍ 按胜率动态收放搜索半径

这段逻辑处在优化器的步长更新环节,核心是用 successRate(本轮改进成功率)反向调节每个维度的 alpha[c] 搜索半径。全局搜索阶段,成功率低于 0.05 时把 alpha[c] 乘 1.2 略微放大范围;高于 0.7 则乘 0.9 收紧,避免在无价值区域空转。 局部搜索阶段更激进:成功率低于 0.05 用 t3 系数扩圈,高于 0.2 直接乘 0.8 缩圈。也就是说,越容易出改进解,算法越倾向精细挖掘,反之才放宽网。 最后一段是硬边界,maxAlpha 锁在维度跨度的 0.2 倍、minAlpha 不低于 0.0001 倍,越界即拉回。外汇与贵金属行情跳变频繁,这种上下限能防止参数在极端 K 线后跑飞,但策略本身仍属概率博弈,实盘前请在 MT5 用历史数据跑通再上模拟盘。

MQL5 / C++
class=class="str">"cmt">// Few improvements - slightly expanding the scope
              alpha [c] *= class="num">1.2;
            }
          else
            if (successRate > class="num">0.7)
            {
              class=class="str">"cmt">// Many improvements - narrowing the scope
              alpha [c] *= class="num">0.9;
            }
        }
      else
        {
          class=class="str">"cmt">// In the local search phase
          if (successRate < class="num">0.05)
          {
            class=class="str">"cmt">// Very few improvements - increasing the search scope
            alpha [c] *= t3;
          }
          else
            if (successRate > class="num">0.2)
            {
              class=class="str">"cmt">// Enough improvements - narrowing the scope
              alpha [c] *= class="num">0.8;
            }
        }
      class=class="str">"cmt">// Limits on alpha range with safe bounds verification
      if (c < ArraySize(rangeMax) && c < ArraySize(rangeMin))
      {
        class="type">class="kw">double maxAlpha = class="num">0.2 * (rangeMax [c] - rangeMin [c]);
        class="type">class="kw">double minAlpha = class="num">0.0001 * (rangeMax [c] - rangeMin [c]);
        if (alpha [c] > maxAlpha)
        {
          alpha [c] = maxAlpha;
        }
        else
          if (alpha [c] < minAlpha)
          {
            alpha [c] = minAlpha;
          }
      }
    }
  }
}
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到参数分布与停滞预警,你只需判断要不要手动干预搜索范围。

常见问题

重置速度可避免历史速度偏向旧坐标,让新变异值不被惯性带偏,保持搜索的随机性与安全性。
占比过低说明约束过强、种群多样性差,提高惩罚能放松边界压力,促使智能体探索更多区域。
三者生成不同混沌序列,提供伪随机与遍历性,在初始搜索与局部开发阶段交替使用以覆盖解空间。
可以,小布的品种页内置了种群分布与停滞检测的可视化,省去你自己写日志解析的麻烦。
它在初始混沌搜索之后的解优化阶段生效,利用梯度信息引导智能体向更优区域移动。