中心引力优化(CFO)算法·进阶篇
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] 确认注入值,外汇与贵金属行情高波动,算法参数误设可能导致信号剧烈漂移。
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 的条件意味着劣解不会拉动优解,外汇与贵金属参数寻优中该机制可能降低早熟收敛概率,但实盘外置优化仍属高风险。
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。注意这里用『大于』判定,说明该框架默认优化目标是最大化适应度,做外汇参数寻优时若用回撤最小化得改符号。
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,往往能肉眼看到尾部优化器逃逸局部最优的能力变化。
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 看直方图——别信纸面排名,自己测完再决定要不要改早期反向吸引。外汇贵金属参数寻优高风险,回测亮眼也可能实盘翻车。