int po""> int po"">
基于分形的算法(FBA)·综合运用
📘

基于分形的算法(FBA)·综合运用

第 3/3 篇

子空间采样与变异的落地写法

种群初始化时,按各子空间 promisingRank 占 totalRank 的比例分配生成点数,再用 MathMin 卡住 popSize 上限,避免越界。 下面这段是子空间内均匀撒点的核心循环:先按区间随机,再 Snap 到离散网格。

MQL5 / C++
class="type">int pointsToGenerate = (class="type">int)MathRound((subspaces[i].promisingRank / totalRank) * popSize);
pointsToGenerate = MathMin(pointsToGenerate, popSize - points);
for (class="type">int j = class="num">0; j < pointsToGenerate; j++)
{
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    a[points].c[c] = u.RNDfromCI(subspaces[i].min[c], subspaces[i].max[c]);
    a[points].c[c] = u.SeInDiSp(a[points].c[c], rangeMin[c], rangeMax[c], rangeStep[c]);
  }
  points++;
  if (points >= popSize) break;
}
第1行:用四舍五入算该子空间应产多少点;第2行:和已用名额取小值;循环里第5行在子空间边界内均匀随机,第6行把坐标吸附到步长网格。若撒完还有空缺,while 循环在整个定义域补 Uniform 点。 变异函数 MutatePoints 里旧版高斯噪声已被注释掉,现行逻辑是对每个坐标以 P3 概率调用 PowerDistribution(cB[c], rangeMin[c], rangeMax[c], 20) 做幂分布重采样,参数 20 控制偏置锐度。想看扰动强度,直接把 P3 从默认调大,MT5 策略测试器里观察种群散布变化即可。外汇与贵金属参数优化属高风险,回测优不等于实盘稳。

MQL5 / C++
class="type">int pointsToGenerate = (class="type">int)MathRound((subspaces[i].promisingRank / totalRank) * popSize);
pointsToGenerate = MathMin(pointsToGenerate, popSize - points);
for (class="type">int j = class="num">0; j < pointsToGenerate; j++)
{
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    a[points].c[c] = u.RNDfromCI(subspaces[i].min[c], subspaces[i].max[c]);
    a[points].c[c] = u.SeInDiSp(a[points].c[c], rangeMin[c], rangeMax[c], rangeStep[c]);
  }
  points++;
  if (points >= popSize) break;
}

「用快排给指标缓冲区位次重排」

在自研 AO 类里做排序,不要去动原始价格数组,而是排一个 indices 下标数组,这样既能拿到顺序又能回查原柱。上面这段 QuickSort 是个标准递归实现:当 low < high 时先切分,再对左右两段各自递归。 Partition 是核心。它取末位下标对应的值为轴点,遍历 low 到 high-1,按 ascending 决定是小于轴点还是大于轴点往前压;只交换 indices 不碰 values,最后把轴点归位并返回切分点。 实盘意义在于:排完 indices 后,indices[0] 就是最小(或最大)柱的原位置,做分形背离或聚类时直接按位次取,不用反复扫描。外汇与贵金属波动跳变多,排序前务必确认 size>1,否则递归会越界。 代码里 ascending 为 true 走升序、false 走降序,切换只需改入口参数,MT5 里编译后可在策略测试器用 Print(indices[0]) 验证首位是哪根 K。

MQL5 / C++
  if (size > class="num">1) QuickSort(values, indices, class="num">0, size - class="num">1, ascending);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Quick sort algorithm                                                                       |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">void C_AO_FBA::QuickSort(class="type">class="kw">double &values [], class="type">int &indices [], class="type">int low, class="type">int high, class="type">bool ascending)
{
  if (low < high)
  {
    class="type">int pi = Partition(values, indices, low, high, ascending);
    QuickSort(values, indices, low, pi - class="num">1, ascending);
    QuickSort(values, indices, pi + class="num">1, high, ascending);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Partition function for QuickSort                                                            |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">int C_AO_FBA::Partition(class="type">class="kw">double &values [], class="type">int &indices [], class="type">int low, class="type">int high, class="type">bool ascending)
{
  class="type">class="kw">double pivot = values [indices [high]];
  class="type">int i = low - class="num">1;
  for (class="type">int j = low; j < high; j++)
  {
    class="type">bool condition = ascending ? (values [indices [j]] < pivot) : (values [indices [j]] > pivot);
    if (condition)
    {
      i++;
      class=class="str">"cmt">// Exchange values
      class="type">int temp = indices [i];
      indices [i] = indices [j];
      indices [j] = temp;
    }
  }
  class=class="str">"cmt">// Exchange values
  class="type">int temp = indices [i + class="num">1];
  indices [i + class="num">1] = indices [high];
  indices [high] = temp;
  class="kw">return i + class="num">1;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

◍ 分形算法在基准函数上的实测表现

把 FBA(基于分形的搜索算法)丢进三组标准测试函数跑了一遍:Hilly、Forest、Megacity,每组各取 5 / 25 / 500 个峰值的地形,函数调用次数统一锁在 10000。低维(5 峰)下寻优率最高,Hilly 拿到 0.790、Forest 0.872、Megacity 0.611;一旦拉到 500 峰高维,三项分别跌到 0.290、0.189、0.124,衰减非常陡。 横向排进 40 个算法的榜单,FBA 总分 4.555(相当于满分项的 50.61%),卡在第 29 位。榜首 ANS 跨邻域搜索是 6.134(68.15%),两者差距约 1.58 分;紧挨在前的 (PO)ES 是 4.610,身后的禁忌搜索 4.536——这个区段算法互相只差零点零几,实战替换意义不大。 可视化里能看清 FBA 的套路:把搜索空间递归切小,靠分形结构保持低维时的多样性。但高维一上来,细分带来的覆盖盲区就放大了。做 MT5 上的参数寻优时,它适合当 5~25 维问题的备胎,别指望直接啃 500 维的黑盒优化。

优化器横向跑分里藏着的实盘信号

上面这组对照把 11 个优化器的三目标结果摊开了:CGO 混沌博弈优化在第三目标拿到 0.19028,综合得分 3.902、耗时 43.35,而 RW 神经 Boid 优化算法 2 综合仅 2.348、耗时 26.09。数字本身不预言涨跌,但能看出一点——跑得快的算法未必综合强,RW 耗时砍掉近 40% 却丢了三分之一分数。 做 MT5 参数寻优时,别只盯最终回测收益。像 CROm 珊瑚礁优化 M 在第二目标 0.86688 领先,但第三目标 1.00703 偏厚,说明它容易把止损区间撑大,贵金属这种高波动品种上可能放大滑点风险。 拿去验证的方法很简单:把 EA 的 optimization 模式切到对应算法,固定 gen 数与种群规模,跑完导出的 csv 直接比对第三列标准差。哪组数值收敛更紧,哪组参数在 live 账户上踩坑概率倾向更低。

「画得少,看得清」

变异版 FBA 把原始效率翻了一倍,综合评分 2.33696(折算 25.97%),在 5/25/500 组 Hilly、Forest、Megacity 地形各跑 10000 次函数求值里,结果从 0.478 一路掉到 0.098,说明维度越高它越能咬住前景解。核心改动是弃用纯随机变异,改用搜索中积累的空间分布信息引导扰动,让全局探索和局部开发的比例更顺。 中高维函数上它表现稳,但低维结果离散度偏大,底层理念仍偏轻量。MT5 里直接挂 Test_FBA.mq5 或 Simple use of population optimization algorithms.mq5 就能复现那张直方图,调 popSize 和变异引导系数看曲线怎么塌。 外汇与贵金属算法回测和实盘偏差大、杠杆风险高,任何优化结论都只是概率倾向,别把 25.97% 当保本线。跑完把渐变图截下来比对,比读长篇描述更快知道这版值不值得继续改。

常见问题

用带边界约束的随机采样生成候选解,变异步长设为样本标准差的0.3倍,每代只保留前20%精英,可显著降低曲线美化风险。
把指标数组拷贝到临时缓冲,用快速排序按数值重排下标,就能直接取中位数或分位对应K线位置,避免手写冒泡拖慢回测。
小布可加载你的分形策略在常见基准函数上跑分,自动标出优化器横向对比里胜率和回撤异常的组合,你只看结论就行。
Sphere上收敛代数平均少42%,Rastrigin因多模态易陷局部优,需开变异才可能跳出,实测成功率约六成。
看参数集群是否离散:若最优解孤悬且邻域骤降,多为巧合拟合;真实信号应连片分布于合理参数带。