中心引力优化(CFO)算法·进阶篇
📘

中心引力优化(CFO)算法·进阶篇

第 2/2 篇

CFO智能体类的参数与初始化骨架

这段 MQL5 代码定义了一个基于中心力优化(CFO)算法的智能体类 C_AO_CFO,核心参数通过 params 数组下标 2~6 注入:alpha 控制质量幂次,beta 控制距离幂次,initialFrep 与 finalFrep 分别是重定位因子的起止值,noiseFactor 为随机噪声强度,frep 初始化为 initialFrep。 类内部还声明了引力常数 g、探测体数组 probe[],以及私有成员 epochs(总迭代代数)、epochNow(当前代)、frep(当前重定位因子),并预留了 InitialDistribution、UpdateRepFactor、CalculateAccelerations、UpdatePositions 等私有方法,构成完整的优化循环骨架。 Init 函数接收区间最小值、最大值、步长三个数组及可选 epochsP 参数(默认 0),先调用 StandardInit 做基础校验,失败直接返回 false;通过后才把 epochsP 赋给 epochs、epochNow 置 0。在 MT5 里新建 EA 调用该类时,建议先打印 params[2]~[6] 确认注入值,外汇与贵金属行情高波动,算法参数误设可能导致信号剧烈漂移。

MQL5 / C++
alpha       = params [class="num">2].val;
beta        = params [class="num">3].val;
initialFrep = params [class="num">4].val;
finalFrep   = params [class="num">5].val;
noiseFactor = params [class="num">6].val;
frep        = initialFrep;
}
class="type">bool Init(const class="type">class="kw">double &rangeMinP  [],  class=class="str">"cmt">// minimum values
           const class="type">class="kw">double &rangeMaxP  [],  class=class="str">"cmt">// maximum values
           const class="type">class="kw">double &rangeStepP [],  class=class="str">"cmt">// step change
           const class="type">int    epochsP = class="num">0);   class=class="str">"cmt">// number of epochs
class="type">void Moving();
class="type">void Revision();
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double g;             class=class="str">"cmt">// gravitational constant 
class="type">class="kw">double alpha;         class=class="str">"cmt">// power for mass
class="type">class="kw">double beta;          class=class="str">"cmt">// degree for distance
class="type">class="kw">double initialFrep;   class=class="str">"cmt">// initial repositioning factor
class="type">class="kw">double finalFrep;     class=class="str">"cmt">// final repositioning factor
class="type">class="kw">double noiseFactor;   class=class="str">"cmt">// random noise factor
S_CFO_Agent probe []; class=class="str">"cmt">// array of probes
class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
class="type">int    epochs;        class=class="str">"cmt">// total number of epochs
class="type">int    epochNow;      class=class="str">"cmt">// current epoch
class="type">class="kw">double frep;          class=class="str">"cmt">// repositioning factor
class="type">void   InitialDistribution();
class="type">void   UpdateRepFactor();
class="type">void   CalculateAccelerations();
class="type">void   UpdatePositions();
class="type">class="kw">double CalculateDistanceSquared(const class="type">class="kw">double &x1 [], const class="type">class="kw">double &x2 []);
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Initialization
class="type">bool C_AO_CFO::Init(const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">// minimum values
                     const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">// maximum values
                     const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// step change
                     const class="type">int    epochsP = class="num">0)
{
 if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return false;
 class=class="str">"cmt">//----------------------------------------------------------------------------
 epochs   = epochsP;
 epochNow = class="num">0;

◍ CFO 算法里探针怎么初始化和迭代

中心力优化(CFO)把每个候选解当成带「质量」的探针,质量由适应度函数 f 决定。初始化阶段先按种群规模 popSize 开数组,再给每个探针在参数区间里随机撒点,并把 f 预设为 -DBL_MAX,保证第一轮任何对手的质量差都为正,从而能算出加速度。 Moving() 是每代的主入口:第一次进函数只做 InitialDistribution() 然后置 revision 标记返回;之后每代先把基类 a[] 里的 f 同步给 probe[],再更新重定位因子、算加速度、更新坐标,最后把 probe 的新位置写回基类。 重定位因子 frep 走线性衰减:frep = initialFrep - (initialFrep - finalFrep) * epochNow / epochs,且被夹在 [0.0, 1.0]。epochs 为 0 时直接保持初值,这种写法在调参时可先令 epochs=0 观察不衰减下的搜索路径。 加速度计算是双层循环:对探针 p 清加速度后,遍历其他探针 k,仅当 massDiff = probe[k].f - probe[p].f > 0 才累加引力贡献。这个严格大于 0 的条件意味着劣解不会拉动优解,外汇与贵金属参数寻优中该机制可能降低早熟收敛概率,但实盘外置优化仍属高风险。

MQL5 / C++
  class=class="str">"cmt">// Initialization of probes
  ArrayResize(probe, popSize);
  for (class="type">int i = class="num">0; i < popSize; i++) probe [i].Init(coords);
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- The main step of the algorithm
class="type">void C_AO_CFO::Moving()
{
  epochNow++;
  class=class="str">"cmt">// Initial initialization
  if (!revision)
  {
    InitialDistribution();
    revision = true;
    class="kw">return;
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Copy the fitness function values from the base class
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    probe [p].f = a [p].f;
  }
  class=class="str">"cmt">// Update the repositioning parameter
  UpdateRepFactor();
  class=class="str">"cmt">// Main CFO loop
  CalculateAccelerations();
  UpdatePositions();
  class=class="str">"cmt">// Synchronize positions with the base class
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    ArrayCopy(a [p].c, probe [p].c);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Initial probe distribution
class="type">void C_AO_CFO::InitialDistribution()
{
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    ArrayInitialize(probe [p].a, class="num">0.0);
    probe [p].f = -DBL_MAX;
    class=class="str">"cmt">// Random distribution
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      probe [p].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
      probe [p].c [c] = u.SeInDiSp(probe [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
    ArrayCopy(a [p].c, probe [p].c);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Update the repositioning factor
class="type">void C_AO_CFO::UpdateRepFactor()
{
  class=class="str">"cmt">// Linearly decrease frep from the initial to the final value
  if (epochs > class="num">0) frep = initialFrep - (initialFrep - finalFrep) * epochNow / epochs;
  else frep = initialFrep;
  class=class="str">"cmt">// Value constraint
  frep = MathMax(class="num">0.0, MathMin(class="num">1.0, frep));
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Calculate accelerations
class="type">void C_AO_CFO::CalculateAccelerations()
{
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    class=class="str">"cmt">// Reset the acceleration for the current probe
    ArrayInitialize(probe [p].a, class="num">0.0);
    class=class="str">"cmt">// Summarize the influence of all other probes
    for (class="type">int k = class="num">0; k < popSize; k++)
    {
      if (k == p) class="kw">continue;
      class=class="str">"cmt">// Difference in masses(fitness values)
      class="type">class="kw">double massDiff = probe [k].f - probe [p].f;
      class=class="str">"cmt">// Check the condition of the U(...) unit function
      if (massDiff > class="num">0) class=class="str">"cmt">// Strict condition for the unit function
      {
        class=class="str">"cmt">// Calculate the distance between probes
        class="type">class="kw">double distSquared = CalculateDistanceSquared(probe [k].c, probe [p].c);

「中心力优化里的位置更新与距离计算」

这段中心力优化(CFO)实现里,粒子加速度由两两之间的中心力叠加得到:先判断距离平方是否小于 DBL_EPSILON,避免除零;再开平方得真实距离,按 g * massDiff^alpha / distance^beta 累加各维度加速度。 UpdatePositions 里位置更新用了 0.5 * a[c] 的半步长位移,并叠加随迭代衰减的随机噪声:currentNoiseFactor 在 epochs>0 时按 (1 - epochNow/epochs) 线性下降,第 0 代噪声最大,末期趋近零。越界直接夹到 rangeMin/rangeMax,再用 SeInDiSp 做离散步长离散化。 距离计算返回平方值而非开方结果,调用方在需要真实距离时再 MathSqrt,少做一次 sqrt 能省下可观的回测耗时——在 popSize=50、coords=10 的参数空间里,每代少算约 1225 次开方。 Revision 只做一件事:遍历种群,若某粒子适应度 a[p].f 优于当前最优 fB 就覆盖 cB 与 fB。注意这里用『大于』判定,说明该框架默认优化目标是最大化适应度,做外汇参数寻优时若用回撤最小化得改符号。

MQL5 / C++
if (distSquared < DBL_EPSILON) class="kw">continue;
class="type">class="kw">double distance = MathSqrt(distSquared);
for (class="type">int c = class="num">0; c < coords; c++)
{
  class=class="str">"cmt">// Force direction
  class="type">class="kw">double direction = (probe [k].c [c] - probe [p].c [c]) / distance;
  class=class="str">"cmt">// Acceleration equation
  probe [p].a [c] += g * MathPow(massDiff, alpha) * direction / MathPow(distance, beta);
}
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Update positions
class="type">void C_AO_CFO::UpdatePositions()
{
  class=class="str">"cmt">// Random noise ratio decreases with increasing epoch number
  class="type">class="kw">double currentNoiseFactor = noiseFactor;
  if (epochs > class="num">0) currentNoiseFactor *= (class="num">1.0 - (class="type">class="kw">double)epochNow / epochs);
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class=class="str">"cmt">// Update the position by equation
      probe [p].c [c] += class="num">0.5 * probe [p].a [c];
      class=class="str">"cmt">// Add a small random offset directly to the position
      probe [p].c [c] += currentNoiseFactor * g * u.RNDfromCI(-class="num">1.0, class="num">1.0);
      class=class="str">"cmt">// Reposition when going out of bounds
      if (probe [p].c [c] < rangeMin [c]) probe [p].c [c] = rangeMin [c];
      if (probe [p].c [c] > rangeMax [c]) probe [p].c [c] = rangeMax [c];
      class=class="str">"cmt">// Discretization if step is specified
      probe [p].c [c] = u.SeInDiSp(probe [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Calculate distance(returns squared distance for optimization)
class="type">class="kw">double C_AO_CFO::CalculateDistanceSquared(const class="type">class="kw">double &x1 [], const class="type">class="kw">double &x2 [])
{
  class="type">class="kw">double sum = class="num">0.0;
  for (class="type">int i = class="num">0; i < coords; i++)
  {
    class="type">class="kw">double diff = x1 [i] - x2 [i];
    sum += diff * diff;
  }
  class="kw">return sum;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Update the best solution
class="type">void C_AO_CFO::Revision()
{
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    if (a [p].f > fB)
    {
      fB = a [p].f;
      ArrayCopy(cB, a [p].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

确定性搜索为何败给随机扰动

中心力优化(CFO)在纯确定性版本下跑完三组基准函数,总评分仅 1.95090,相当于满分潜力的 21.68%,连评级表都进不去。具体看:5 Hilly 得 0.345、25 Hilly 得 0.283、500 Hilly 得 0.252;Megacity 离散函数在 500 规模下只剩 0.095。这种严格按引力公式迭代的探测器,容易卡在已扫过的轨迹里,缺少跳出局部结构的机制。 在探测器运动里叠一小段随机偏差后,同样 10000 次函数调用,总评分跳到 3.66835(40.76%)。5 Hilly 从 0.345 升到 0.610,5 Forest 从 0.262 升到 0.634,说明随机性让群体探进了原本不会走的方向。但 500 规模的高维项仍偏弱——500 Megacity 仅 0.096,和确定性版几乎持平。 把可视化跑一遍会发现,CFO 在中维(25 规模)函数上表现最稳,低维和高维都吃力。最终它在 42 个对比算法里排第 42 位,前面 ANS、CLA 等头部算法总评分在 5.2–6.1 区间(58%–68%)。若你想在 MT5 里复现,先把随机系数从 0 调到 1.0 看评分曲线,再考虑是否值得接入实盘信号层。外汇与贵金属市场高风险,此类优化结果仅作算法验证参考,不构成交易胜率保证。

◍ 尾部优化器的实测落差

上面这组横向对比把 30 到 45 号优化器以及随机游走(RW)的回测指标摊开了:30 号 BSO 综合得分 4.498、胜率类指标 49.98%,而 45 号 MEC 掉到 3.470、38.55%,RW 更是只有 2.348、26.09%。越靠后的元启发式算法,在 MT5 策略测试器里的稳健性倾向明显走弱,但个别项(如 40 号 CGO 第二项 0.61931)仍可能局部占优,不能单看总分枪毙。 外汇与贵金属品种上跑这套优化,杠杆与滑点会放大参数脆弱性,属于高风险验证,任何算法给出的优势都只是概率倾向。 代码里那行直接给仓位坐标加噪的操作,是这类随机扰动类优化的底层动作: // Add a small random offset directly to the position probe [p].c [c] += currentNoiseFactor * g * u.RNDfromCI (-1.0, 1.0); 意思是拿当前噪声系数乘全局步长 g,再乘一个 [-1,1] 均匀随机量,直接叠到探针第 p 个个体的第 c 维坐标上。开 MT5 把 currentNoiseFactor 从 0.1 调到 0.5,往往能肉眼看到尾部优化器逃逸局部最优的能力变化。

MQL5 / C++
class=class="str">"cmt">// Add a small random offset directly to the position
probe [p].c [c] += currentNoiseFactor * g * u.RNDfromCI(-class="num">1.0, class="num">1.0);

「记住这一条就够了」

CFO 在标准测试集上只跑到理论峰值的 40% 出头,在 45 个群体算法里排第 42,靠的是往原本确定性的探测器运动里塞了随机扰动才换来这点提升。 它胜在物理直觉直白:差解被好解像引力一样吸过去,代码也好落,中等维度函数够用。但初始探测器摆位几乎锁死终局,单向吸引又压住了探索宽度,低维、高维和离散函数都偏弱。 真要上手,去 GitHub 把 CFO.zip 拖进 MT5,跑 Test_AO_CFO.mq5 看直方图——别信纸面排名,自己测完再决定要不要改早期反向吸引。外汇贵金属参数寻优高风险,回测亮眼也可能实盘翻车。

常见问题

主要看种群规模、搜索空间边界和初始探针分布;规模太小易陷局部最优,建议先用中等规模做敏感性测试再定。
位置用向量存储,距离取欧氏范数并加极小epsilon防除零;迭代时按合力方向加权更新,注意边界回弹处理。
可以,小布能按你给的边界自动生成初始探针并并行跑多组参数,把收敛曲线和落差直接标在品种页上。
确定性步长容易卡在平坦区,随机扰动提供逃逸概率,实测尾部优化器随机项开启后胜率倾向更高。
正常,样本外普遍衰减;建议只拿它做候选池筛选,实盘前用分段窗口复核,贵金属外汇属高风险勿直接跟。