神经Boid优化算法2(NOA2)·综合运用
🧠

神经Boid优化算法2(NOA2)·综合运用

(3/3)· 当Boid群智能遇上个体神经网络,前篇铺垫的模型如何在真实函数上跑出探索与开发的平衡

实战向 第 3/3 篇
不少交易者把集群优化当成黑箱丢给默认参数,结果在窄幅震荡品种上反复踩坑。NOA2 里每个智能体的神经网络都在改写标准 Boid 规则,忽视这点就会误判它的收敛行为。看清个体学习机制,才谈得上把它接进自己的 EA。

◍ 停滞重启与群体聚散的代码落点

优化器陷入停滞时,代码会把探索率按 1.5 倍放大但封顶 0.5,同时每累计 50 次无进展就触发部分重启:淘汰尾部 70% 个体、保留最优代理,并在边界内随机重撒坐标与微小速度。 若进展正常,探索率回退到参数位 [11] 的值,未配置时掉回 0.1 默认值——这意味着你调参时第 12 个输入直接决定「顺风顺水时的保守程度」。 聚合力 Cohesion 按邻居质量加权求质心:只纳入距离小于 distanceMax*cohesionDist 的代理,用各自质量 agent[i].m 做权重,再乘 cohesionWeight 拽向中心。分离力 Separation 则反向推开避免重叠,两者权重比例会明显改变种群在参数空间的铺开形态。 外汇与贵金属行情高波动、滑点随机,这类群体算法在 MT5 中回测大概率对止损与采样区间敏感,建议先固定 popSize=50、restart 比例 0.7 跑一轮 EURUSD 的 H1 观察收敛节奏。

MQL5 / C++
explorationRate = MathMin(class="num">0.5, explorationRate * class="num">1.5);
class=class="str">"cmt">// Perform a partial restart every class="num">50 stagnation iterations
if (m_stagnationCounter % class="num">50 == class="num">0)
{
  class=class="str">"cmt">// Restart class="num">70% of the population leaving the best agents
  class="type">int restart_count = (class="type">int)(popSize * class="num">0.7);
  for (class="type">int i = popSize - restart_count; i < popSize; i++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      agent [i].x [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
      agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI(-class="num">1.0, class="num">1.0) * class="num">0.001;
    }
  }
  class=class="str">"cmt">// Reset the stagnation counter
  m_stagnationCounter = class="num">0;
}
else
{
  class=class="str">"cmt">// If progress is good enough, use the normal research level 
  if (class="num">11 < ArraySize(params))
  {
    explorationRate = params [class="num">11].val;
  }
  else
  {
    explorationRate = class="num">0.1; class=class="str">"cmt">// class="kw">default value
  }
}
}
class=class="str">"cmt">// Find the center of mass of other boids and adjust the speed towards the center of mass
class="type">void C_AO_NOA2::Cohesion(S_NeuroBoids_Agent &boid, class="type">int pos)
{
  class="type">class="kw">double centerX [];
  ArrayResize(centerX, coords);
  ArrayInitialize(centerX, class="num">0.0);
  class="type">int numNeighbors = class="num">0;
  class="type">class="kw">double sumMass = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (pos != i) sumMass += agent [i].m;
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (pos != i)
    {
      if (Distance(boid, agent [i]) < distanceMax * cohesionDist)
      {
        for (class="type">int c = class="num">0; c < coords; c++)
        {
          centerX [c] += agent [i].x [c] * agent [i].m;
        }
        numNeighbors++;
      }
    }
  }
  if (numNeighbors > class="num">0 && sumMass > class="num">0.0)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      centerX [c] /= sumMass;
      boid.dx [c] += (centerX [c] - boid.x [c]) * cohesionWeight;
    }
  }
}
class=class="str">"cmt">// Pushing away from other boids to avoid collisions
class="type">void C_AO_NOA2::Separation(S_NeuroBoids_Agent &boid, class="type">int pos)
{
  class="type">class="kw">double moveX [];
  ArrayResize(moveX, coords);
  ArrayInitialize(moveX, class="num">0.0);
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (pos != i)
    {

「分离、对齐与限速的三段式推进」

这段 NeuroBoids 的群体逻辑里,分离阶段先判断邻居距离是否小于 distanceMax 乘 separationDist,命中就把对方相对坐标差累加进 moveX,最后统一乘 separationWeight 回写速度。它只处理「太近就推开」,不关心方向优劣,所以权重调大容易让个体乱窜。 对齐函数 Alignment 则反过来收集邻域内 agent 的各轴速度,按 numNeighbors 取平均,再让当前 boid 的速度向均值靠拢,靠拢幅度由 alignmentWeight 控制。若邻域为空(numNeighbors=0),这步直接跳过,个体保持原速。 LimitSpeed 用平方和开根号算总速,低于 speedMax[0]*minSpeed 会被拉到最小速,高于 speedMax[0]*maxSpeed 则压回最大速;速近零时给各轴塞一个 (-1.0,1.0) 的随机微速避免死锁。外汇与贵金属行情用这类模型做聚类试探时,参数敏感度高,实盘前务必在 MT5 策略测试器里跑多品种回测。

MQL5 / C++
if (Distance(boid, agent [i]) < distanceMax * separationDist)
{
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    moveX [c] += boid.x [c] - agent [i].x [c];
  }
}
}
}
for (class="type">int c = class="num">0; c < coords; c++)
{
  boid.dx [c] += moveX [c] * separationWeight;
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Align speed with other boids
class="type">void C_AO_NOA2::Alignment(S_NeuroBoids_Agent &boid, class="type">int pos)
{
  class="type">class="kw">double avgDX [];
  ArrayResize(avgDX, coords);
  ArrayInitialize(avgDX, class="num">0.0);
  class="type">int numNeighbors = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (pos != i)
    {
      if (Distance(boid, agent [i]) < distanceMax * alignmentDist)
      {
        for (class="type">int c = class="num">0; c < coords; c++)
        {
          avgDX [c] += agent [i].dx [c];
        }
        numNeighbors++;
      }
    }
  }
  if (numNeighbors > class="num">0)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      avgDX [c] /= numNeighbors;
      boid.dx [c] += (avgDX [c] - boid.dx [c]) * alignmentWeight;
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Speed limit
class="type">void C_AO_NOA2::LimitSpeed(S_NeuroBoids_Agent &boid)
{
  class="type">class="kw">double speed = class="num">0;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    speed += boid.dx [c] * boid.dx [c];
  }
  speed = MathSqrt(speed);
  class=class="str">"cmt">// If the speed is not zero(prevent division by zero)
  if (speed > class="num">1e-10)
  {
    class=class="str">"cmt">// If the speed is too low, increase it
    if (speed < speedMax [class="num">0] * minSpeed)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        boid.dx [c] = boid.dx [c] / speed * speedMax [c] * minSpeed;
      }
    }
    class=class="str">"cmt">// If the speed is too high, reduce it
    else
    if (speed > speedMax [class="num">0] * maxSpeed)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        boid.dx [c] = boid.dx [c] / speed * speedMax [c] * maxSpeed;
      }
    }
  }
  else
  {
    class=class="str">"cmt">// If the speed is almost zero, set a small random speed
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      boid.dx [c] = u.RNDfromCI(-class="num">1.0, class="num">1.0) * speedMax [c] * minSpeed;
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

边界反弹与群体距离度量的代码内核

神经群算法里每个 agent 不能无限制漂出搜索域,KeepWithinBounds 做的就是边界硬约束:某一维坐标低于 rangeMin 就速度反向并加 0.1% 区间宽度的推力,高于 rangeMax 则反向减同样推力,避免贴边死锁。 Distance 用欧氏距离的平方根衡量两个 boid 的差距,逐维平方累加再开方,coords 维空间通用。回测中若把 coords 设为 2(比如两参数寻优),这个距离直接决定分离与聚合力的权重。 Revision 负责刷新全局最优:当某个 agent 的适应度 f 大于记录值 fB 就覆盖 cB 并清零停滞计数器;若前后最优差异小于 0.000001 视为无进展,explorationRate 按 params[11].val*0.5 与自身 0.9 倍取大值收缩,逼算法从探索转向利用。外汇与贵金属参数寻优高风险,过拟合可能让实盘概率优势消失。 让小布替你跑这套:把上面三段直接贴进 MT5 的 include 类文件,改 coords 和 popSize 就能在 EURUSD 15M 上验证收敛速度。

MQL5 / C++
class=class="str">"cmt">// Keep the boid within the boundaries. If it gets too close to the edge,
class=class="str">"cmt">// push it back and change direction.
class="type">void C_AO_NOA2::KeepWithinBounds(S_NeuroBoids_Agent &boid)
{
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    if (boid.x [c] < rangeMin [c])
    {
      boid.dx [c] *= -class="num">1.0;
      class=class="str">"cmt">// Add a small push from the border
      boid.dx [c] += (rangeMax [c] - rangeMin [c]) * class="num">0.001;
    }
    if (boid.x [c] > rangeMax [c])
    {
      boid.dx [c] *= -class="num">1.0;
      class=class="str">"cmt">// Add a small push from the border
      boid.dx [c] -= (rangeMax [c] - rangeMin [c]) * class="num">0.001;
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Calculate the distance between two boids
class="type">class="kw">double C_AO_NOA2::Distance(S_NeuroBoids_Agent &boid1, S_NeuroBoids_Agent &boid2)
{
  class="type">class="kw">double dist = class="num">0;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    dist += MathPow(boid1.x [c] - boid2.x [c], class="num">2);
  }
  class="kw">return MathSqrt(dist);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">// Update the best solution found
class="type">void C_AO_NOA2::Revision()
{
  class=class="str">"cmt">// Update the best coordinates and fitness function value
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class=class="str">"cmt">// Update the global best solution
    if (a [i].f > fB)
    {
      fB = a [i].f;
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        cB [c] = a [i].c [c];
      }
      class=class="str">"cmt">// Reset the stagnation counter when a better solution is found
      m_stagnationCounter = class="num">0;
    }
  }
  class=class="str">"cmt">// Check for progress to adapt the algorithm parameters
  class="type">bool hasProgress = MathAbs(fB - m_prevBestFitness) > class="num">0.000001;
  if (hasProgress)
  {
    m_prevBestFitness = fB;
    explorationRate = MathMax(params [class="num">11].val * class="num">0.5, explorationRate * class="num">0.9);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

◍ NOA2 基准跑分与种群排名位置

把 NOA2 直接丢进三类标准测试函数:Hilly、Forest、Megacity,每个规模跑 10000 次函数调用。Hilly 在 5/25/500 规模下得分 0.4768 / 0.3076 / 0.2545;Forest 同规模 0.3238 / 0.2098 / 0.1574;Megacity 离散场景下 0.2708 / 0.1468 / 0.0973。三项加总 2.24472,折算百分比 24.94%,在 39 个对比算法里排最末。

可视化上能看出 Boid 集群在复杂地形里容易过早收拢,搜索覆盖偏窄。但算法对外暴露了全局参数,改 `50.00.60.0010.0050.030.1` 这一串就能切换邻域半径、分离/对齐/聚合权重,MT5 里接全局变量实时调参能看到行为模式跳变。

外汇与贵金属行情属高风险环境,这类弱基准的群体算法只适合做参数探索辅助,别拿去直接出信号。排进种群优化表仅作信息参考,实盘前务必自己跑回测。

「低维基准里跑分垫底的一批优化器」

上面这组对照把 CGO、ATAm、CFO、ASHA、ASBO、MEC 以及 NOA2 在 13 维测试函数上的结果摊开看,最后两列是综合评分 3.4~3.9 与相对百分比 38%~43%,而 NOA2 直接掉到 2.245 / 24.94。 这些算法在贵金属或外汇参数寻优里如果只跑低维基准,容易给人“够用”的错觉;但 NOA2 相对前几名掉了约 14 个百分点,说明同样迭代预算下收敛稳定性可能明显偏弱。 实盘前建议把候选优化器接进 MT5 的 frame 测试,用你自己的 EURUSD 或 XAUUSD 历史段重跑,别直接信这张表里的中段分数。外汇与贵金属杠杆高,参数过拟合会导致实盘回撤失控。

把 NOA2 当试验田而非实盘武器

神经 Boid 优化算法(NOA2)把集群智能的 Boid 模型与单层神经网络拧在一起:输入层吃坐标、速度、离最优解距离和适应度,输出层吐速度修正值与三条集群规则的自适应参数。当前实现没有隐藏层,输入维度固定为 coords*2+2,输出则是每坐标修正量加 3 个规则参数,调参空间大得离谱。 我在测试函数上把各种参数组合都跑了一遍,仍没揪出能打的理想配置;直方图评分区间 0–100,NOA2 实测得分极低,离 100 的理论上限差着量级。就现在这版,它只是混合优化思路的概念验证,别指望直接拿来寻优。 外汇和贵金属市场高波动、高杠杆,任何优化器上岗前都得先过历史回测与样本外验证。NOA2 的源码包(NOA2.zip,186.45 KB)和测试台 Test_AO_NOA2.mq5 都开放,值得你拉进 MT5 改两层网络或重排输入特征,看评分能不能往上抬。 真要上手,先跑通附带的统一测试平台 Testing AOs.mq5,对照 CalculationTestResults.mqh 出的排名表,确认自己改的版本是否真的优于原版——否则它永远只是个教学标本。

把参数敏感性交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到不同景观下 NOA2 的智能体特化分布,你专注判断要不要换基准函数。

常见问题

每个智能体配备独立的多层网络,从自身运动历史中学习,因此靠近前景区的个体会演化出偏开发的控制模式,稀疏区则保留探索倾向。
传统实现靠固定聚合、分离、对齐权重,NOA2 由神经网络按情境动态调整这三项影响力,探索与开发比例随迭代自行收敛。
高维多模且适应度景观陡峭的函数上,早期过度开发可能导致群体陷入局部峰,概率上优于随机搜索但不保证全局最优。
目前知识库内的 AIGC 模块可识别标准 MT5 优化报告字段,把 NOA2 输出的智能体轨迹按品种页叠加,便于回看空间探索路径。
本篇算法实现与测试结果两节承接基础篇与进阶篇,完整给出自适应运动控制和规则修正的代码级说明与跑分对照。