基于分形的算法(FBA)·进阶篇
🧩

基于分形的算法(FBA)·进阶篇

(2/3)·从棋盘式空间划分到前景区域递归细分,看 FBA 如何把计算资源精准压在最优解附近

含代码示例偏理论 第 2/3 篇
在连续优化里盲目撒点等于把算力扔进黑洞。FBA 用分形自相似性把搜索空间切成层级棋盘,只让多数探索者往排名前 30% 的格子钻,少数去随机游走防漏解——这套思路能直接搬进 EA 的参数寻优。

FBA优化器的初始化与移动逻辑

在 MT5 里跑 FBA(分形布朗优化)这类自定义求解器,第一步是种群初始化。代码里的 Moving() 函数在 revision 为 false 时,会按 rangeMin/rangeMax/rangeStep 把 popSize 个个体均匀撒进搜索空间,并用 SeInDiSp 把坐标吸附到离散网格上——这决定了后续参数寻优不会跳出你设的步长粒度。 初始化只跑一次,随后 revision 置 true 并直接 return,主循环才进入六步主优化:先按适应度挑出 P1% 优解(IdentifyPromisingPoints),再算各子空间潜力秩、选 P2% 子空间、细分、生成新种群、最后变异。每一步都是独立方法调用,方便你在 MT5 策略测试器里单步注释掉做对照。 空间划分有个硬上限:CreateInitialSpacePartitioning() 中 totalSubspaces = m_value^coords,若超过 10000 就截断到 10000。这意味着当坐标维度 coords 较高、m_value 取 3 时,3^8=6561 还能完整分,但 3^9=19683 会被砍掉近一半子空间,维度爆炸时求解覆盖度会倾向下降。 Revision() 负责刷新全局最优点:遍历种群,若 a[i].f > fB 就拷贝坐标进 cB。注意这里用大于号,说明该框架默认适应度是越大越好(如夏普类指标),若你优化的是回撤最小化就得在传参前取负。

MQL5 / C++
class="type">void C_AO_FBA::Moving()
{
   class=class="str">"cmt">// First iteration - initialization of the initial population
   if (!revision)
   {
      class=class="str">"cmt">// Initialize the initial population uniformly throughout the space
      for (class="type">int i = class="num">0; i < popSize; i++)
      {
         for (class="type">int c = class="num">0; c < coords; c++)
         {
            a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
            a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
         }
      }
      revision = true;
      class="kw">return;
   }
   class=class="str">"cmt">// Main optimization
   class=class="str">"cmt">// class="num">1. Identifying promising points(P1% of points with the best function values)
   class="type">int promisingIndices [];
   IdentifyPromisingPoints(promisingIndices);
   class=class="str">"cmt">// class="num">2. Calculation of potential ranks for each subspace
   CalculateSubspaceRanks(promisingIndices);
   class=class="str">"cmt">// class="num">3. Selecting the P2% most promising subspaces
   SelectPromisingSubspaces();
   class=class="str">"cmt">// class="num">4. Dividing promising subspaces into smaller ones
   DividePromisingSubspaces();
   class=class="str">"cmt">// class="num">5. Generating new points taking into account potential ranks
   GenerateNewPopulation();
   class=class="str">"cmt">// class="num">6. Random modification(mutation)
   MutatePoints();
}

class="type">void C_AO_FBA::Revision()
{
   class=class="str">"cmt">// Search for the best solution
   for (class="type">int i = class="num">0; i < popSize; i++)
   {
      class=class="str">"cmt">// Update the best solution
      if (a [i].f > fB)
      {
         fB = a [i].f;
         ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
      }
   }
}

class="type">void C_AO_FBA::CreateInitialSpacePartitioning()
{
   class=class="str">"cmt">// Create an initial partition of space
   class="type">int totalSubspaces = (class="type">int)MathPow(m_value, coords);
   class=class="str">"cmt">// For very large dimensions, limit the number of subspaces
   if (totalSubspaces > class="num">10000) totalSubspaces = class="num">10000;
   ArrayResize(subspaces, totalSubspaces);
   class=class="str">"cmt">// Initialize all subspaces
   for (class="type">int i = class="num">0; i < totalSubspaces; i++)
   {
      subspaces [i].Init(coords);
      subspaces [i].level = class="num">0; class=class="str">"cmt">// Initial level
   }
   class=class="str">"cmt">// Divide the initial space into equal subspaces
   class="type">int index = class="num">0;

「空间切分的维度分支逻辑」

把特征空间切成均匀子空间时,代码按坐标维度走了三条路:一维直接均分,二维套两层循环,多维则用进位迭代。核心都是拿 rangeMax 减 rangeMin 再除以 m_value 得到步长,m_value 就是每条轴切几段。 一维场景里 intervalSize = (rangeMax[0] - rangeMin[0]) / m_value,循环 i 从 0 到 m_value-1,每个子空间 min 和 max 沿轴平移一个步长。二维把两条轴各自算 intervalSize0、intervalSize1,i、j 双循环铺满 m_value 乘 m_value 个格子。 维度大于 2 时硬写循环不现实,代码改用 indices[] 数组模拟进位:从最后一维加 1,满 m_value 归零并向前一位进 1,类似多进制计数器。当 c 退到 -1 说明所有组合已枚举完,直接 break。外汇与贵金属市场的高维特征空间划分涉及高风险,回测结论仅具概率意义。 下面这段是原文的分支实现,逐行拆完就能在 MT5 里改 m_value 看子空间数量变化:if(coords==1) 走一维均分;else if(coords==2) 走双层循环;else 进入多维迭代。注意 index < totalSubspaces 的守卫,避免越界写坏 subspaces 数组。

MQL5 / C++
  class=class="str">"cmt">// Select the division method depending on the dimensionality of the space
  if (coords == class="num">1)
  {
    class=class="str">"cmt">// One-dimensional case
    class="type">class="kw">double intervalSize = (rangeMax [class="num">0] - rangeMin [class="num">0]) / m_value;
    for (class="type">int i = class="num">0; i < m_value && index < totalSubspaces; i++)
    {
      subspaces [index].min [class="num">0] = rangeMin [class="num">0] + i * intervalSize;
      subspaces [index].max [class="num">0] = rangeMin [class="num">0] + (i + class="num">1) * intervalSize;
      index++;
    }
  }
  else
    if (coords == class="num">2)
    {
      class=class="str">"cmt">// Two-dimensional case
      class="type">class="kw">double intervalSize0 = (rangeMax [class="num">0] - rangeMin [class="num">0]) / m_value;
      class="type">class="kw">double intervalSize1 = (rangeMax [class="num">1] - rangeMin [class="num">1]) / m_value;
      for (class="type">int i = class="num">0; i < m_value && index < totalSubspaces; i++)
      {
        for (class="type">int j = class="num">0; j < m_value && index < totalSubspaces; j++)
        {
          subspaces [index].min [class="num">0] = rangeMin [class="num">0] + i * intervalSize0;
          subspaces [index].max [class="num">0] = rangeMin [class="num">0] + (i + class="num">1) * intervalSize0;
          subspaces [index].min [class="num">1] = rangeMin [class="num">1] + j * intervalSize1;
          subspaces [index].max [class="num">1] = rangeMin [class="num">1] + (j + class="num">1) * intervalSize1;
          index++;
        }
      }
    }
    else
    {
      class=class="str">"cmt">// Multidimensional case - use an iterative approach
      class="type">int indices [];
      ArrayResize(indices, coords);
      for (class="type">int i = class="num">0; i < coords; i++) indices [i] = class="num">0;
      class="kw">while (index < totalSubspaces)
      {
        class=class="str">"cmt">// Calculate the boundaries of the current subspace
        for (class="type">int c = class="num">0; c < coords; c++)
        {
          class="type">class="kw">double intervalSize = (rangeMax [c] - rangeMin [c]) / m_value;
          subspaces [index].min [c] = rangeMin [c] + indices [c] * intervalSize;
          subspaces [index].max [c] = rangeMin [c] + (indices [c] + class="num">1) * intervalSize;
        }
        class=class="str">"cmt">// Move on to the next subspace
        class="type">int c = coords - class="num">1;
        class="kw">while (c >= class="num">0)
        {
          indices [c]++;
          if (indices [c] < m_value) break;
          indices [c] = class="num">0;
          c--;
        }
        class=class="str">"cmt">// If the full loop completed, exit
        if (c < class="num">0) break;
        index++;
      }
    }

◍ 子空间筛选与优点的潜力排序

在基于种群的优化框架里,判断某个解是否落在指定子空间,是后续潜力统计的前提。下面这段逻辑用逐维边界比对,只要任一坐标越界就直接判否,效率比整体距离计算高出一个数量级。 bool C_AO_FBA::IsPointInSubspace (const double &point [], const S_Subspace &subspace) { for (int c = 0; c < coords; c++) {

if (point [c] < subspace.min [c]point [c] >= subspace.max [c])

return false; } return true; } 逐行拆解:函数接收待查点 point 与子空间 subspace;循环遍历 coords 个坐标维度;若某维数值小于该维下限或大于等于上限,立刻返回 false;全部维度通过才返回 true,说明点归属于该子空间。 IdentifyPromisingPoints 负责从种群里挑出前 P1% 的优解。它先把所有个体的适应度 a[i].f 抄进 values 数组,下标存进 indices,再调用 SortByFitness 做降序排列;随后用 MathRound(popSize*P1/100.0) 算出入选数量,并用 MathMax/MathMin 夹在 1 到 popSize 之间,避免 P1 设得过小或过大导致空选或全选。 void C_AO_FBA::IdentifyPromisingPoints (int &promisingIndices []) { double values []; int indices []; ArrayResize (values, popSize); ArrayResize (indices, popSize); for (int i = 0; i < popSize; i++) { values [i] = a [i].f; indices [i] = i; } SortByFitness (values, indices, popSize); int numPromisingPoints = (int)MathRound (popSize * P1 / 100.0); numPromisingPoints = MathMax (1, MathMin (numPromisingPoints, popSize)); ArrayResize (promisingIndices, numPromisingPoints); for (int i = 0; i < numPromisingPoints; i++) promisingIndices [i] = indices [i]; } 拆解:前两段建数组并扩容到种群规模;for 循环把适应度和原始序号对齐填充;排序后按百分比截取,最后把排好序的前 N 个原始下标写回 promisingIndices。 CalculateSubspaceRanks 先清零各子空间的 promisingRank,再拿优解逐个问「你归哪个子空间」,命中就给那个子空间计数加一并 break。因为一个点只可能落在一个子空间,break 能省掉剩余比对。外汇与贵金属市场的高波动可能让优解分布偏移,跑 MT5 时把 P1 从默认 10 调到 15 观察子空间热度变化会更直观。

MQL5 / C++
class="type">bool C_AO_FBA::IsPointInSubspace(class="kw">const class="type">class="kw">double &point [], class="kw">const S_Subspace &subspace)
{
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    if (point [c] < subspace.min [c] || point [c] >= subspace.max [c])
      class="kw">return false;
  }
  class="kw">return true;
}

class="type">void C_AO_FBA::IdentifyPromisingPoints(class="type">int &promisingIndices [])
{
  class="type">class="kw">double values  [];
  class="type">int    indices [];
  ArrayResize(values,  popSize);
  ArrayResize(indices, popSize);
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    values  [i] = a [i].f;
    indices [i] = i;
  }
  SortByFitness(values, indices, popSize);
  class="type">int numPromisingPoints = (class="type">int)MathRound(popSize * P1 / class="num">100.0);
  numPromisingPoints = MathMax(class="num">1, MathMin(numPromisingPoints, popSize));
  ArrayResize(promisingIndices, numPromisingPoints);
  for (class="type">int i = class="num">0; i < numPromisingPoints; i++)
    promisingIndices [i] = indices [i];
}

class="type">void C_AO_FBA::CalculateSubspaceRanks(class="kw">const class="type">int &promisingIndices [])
{
  for (class="type">int i = class="num">0; i < ArraySize(subspaces); i++)
    subspaces [i].promisingRank = class="num">0.0;
  for (class="type">int i = class="num">0; i < ArraySize(promisingIndices); i++)
  {
    class="type">int pointIndex = promisingIndices [i];
    for (class="type">int j = class="num">0; j < ArraySize(subspaces); j++)
    {
      if (IsPointInSubspace(a [pointIndex].c, subspaces [j]))
      {
        subspaces [j].promisingRank++;
        break;
      }
    }
  }
}

按排名筛出高潜力子空间并切分

候选子空间算完 promisingRank 后,需要按占比 P2% 挑出最值得继续细分的那些。代码里先建 ranks 和 indices 两个数组,把每个子空间的排名和原始下标存进去,同时把 isPromising 标志复位成 false,避免上一轮残留。 SortByFitness 做降序排列后,用 MathRound(numSubspaces * P2 / 100.0) 算出要保留的数量,再用 MathMax(1, MathMin(..., numSubspaces)) 夹一下边界——哪怕 P2 设得极小也至少留 1 个,设得极大也不超过总数。遍历前 numPromisingSubspaces 个下标,把对应子空间的 isPromising 置 true,这里还加了 indices[i] 越界保护。 挑完之后 DividePromisingSubspaces 只收集 isPromising 为真的下标,挨个调 DivideSubspace 做切分。外汇与贵金属市场波动剧烈、杠杆风险高,这套筛选逻辑在 MT5 里跑时,P2 取值倾向影响搜索粒度:P2 太小可能漏掉边缘有效区间,太大则计算量陡增。开 MT5 把 P2 从 10 调到 30,观察 subspaces 数组长度变化就能验证。

MQL5 / C++
class=class="str">"cmt">// Select P2% subspaces with the highest ranks as promising ones
class=class="str">"cmt">// Create arrays for sorting
class="type">class="kw">double ranks [];
class="type">int indices [];
class="type">int numSubspaces = ArraySize(subspaces);
ArrayResize(ranks, numSubspaces);
ArrayResize(indices, numSubspaces);
class=class="str">"cmt">// Fill in the arrays
for (class="type">int i = class="num">0; i < numSubspaces; i++)
{
   ranks [i] = subspaces [i].promisingRank;
   indices [i] = i;
   class=class="str">"cmt">// Reset the potential flag
   subspaces [i].isPromising = false;
}
class=class="str">"cmt">// Sort by descending ranks
SortByFitness(ranks, indices, numSubspaces);
class=class="str">"cmt">// Select P2% most promising subspaces
class="type">int numPromisingSubspaces = (class="type">int)MathRound(numSubspaces * P2 / class="num">100.0);
numPromisingSubspaces = MathMax(class="num">1, MathMin(numPromisingSubspaces, numSubspaces));
class=class="str">"cmt">// Mark promising subspaces
for (class="type">int i = class="num">0; i < numPromisingSubspaces && i < ArraySize(indices); i++)
{
   class=class="str">"cmt">// Protection against exceeding the array size
   if (indices [i] >= class="num">0 && indices [i] < ArraySize(subspaces))
   {
      subspaces [indices [i]].isPromising = true;
   }
}

class="type">void C_AO_FBA::DividePromisingSubspaces()
{
   class=class="str">"cmt">// Collect indices of promising subspaces
   class="type">int promisingIndices [];
   class="type">int numPromising = class="num">0;
   for (class="type">int i = class="num">0; i < ArraySize(subspaces); i++)
   {
      if (subspaces [i].isPromising)
      {
         numPromising++;
         ArrayResize(promisingIndices, numPromising);
         promisingIndices [numPromising - class="num">1] = i;
      }
   }
   class=class="str">"cmt">// Divide each promising subspace
   for (class="type">int i = class="num">0; i < numPromising; i++)
   {
      DivideSubspace(promisingIndices [i]);
   }
}

「子空间裂变与新种群采样」

FBA 优化器在迭代中靠 DivideSubspace 把某个父空间切成 m_value^coords 个子空间,coords 是维度、m_value 是每维切分数。代码里硬卡了 ArraySize(subspaces) > 10000 就直接 return,说明空间数膨胀到万级会放弃继续细分,实盘跑高维参数寻优时得留意这个隐形天花板。 切片逻辑是逐维均分区间:intervalSize = (parent.max[c] - parent.min[c]) / m_value,新空间 min/max 按 indices[c] 偏移。indices 用进位制遍历,从末维加一、满 m_value 归零并向前进位,等效于在 coords 维网格上枚举全部组合。 GenerateNewPopulation 先累加所有子空间的 promisingRank 作为 totalRank。若 totalRank <= 0.0001 视为全零,则强制把每个空间 rank 置 1.0 并令 totalRank = 空间数,回退成均匀采样,避免早期无信号时种群塌缩到空集。 之后按 rank 占比向各子空间分配 popSize 个候选点(代码截断在循环头,分配细节在下一段)。外汇与贵金属波动剧烈,这种基于历史秩的采样在高波动段可能过拟合,建议用 MT5 策略测试器多周期验证再上实盘。

MQL5 / C++
class="type">void C_AO_FBA::DivideSubspace(class="type">int subspaceIndex)
{
  class=class="str">"cmt">// Divide the specified subspace into m_value^coords subspaces
  S_Subspace parent = subspaces [subspaceIndex];
  class=class="str">"cmt">// Limit the maximum number of subspaces
  if (ArraySize(subspaces) > class="num">10000) class="kw">return;
  class=class="str">"cmt">// For each dimension, divide by m_value parts
  class="type">int totalNewSubspaces = (class="type">int)MathPow(m_value, coords);
  class="type">int currentSize = ArraySize(subspaces);
  ArrayResize(subspaces, currentSize + totalNewSubspaces);
  class=class="str">"cmt">// Create new subspaces
  class="type">int newIndex = currentSize;
  class="type">int indices [];
  ArrayResize(indices, coords);
  for (class="type">int i = class="num">0; i < coords; i++) indices [i] = class="num">0;
  for (class="type">int idx = class="num">0; idx < totalNewSubspaces && newIndex < ArraySize(subspaces); idx++)
  {
    subspaces [newIndex].Init(coords);
    subspaces [newIndex].level = parent.level + class="num">1;
    subspaces [newIndex].parentIndex = subspaceIndex;
    class=class="str">"cmt">// Calculate the boundaries of the current subspace
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class="type">class="kw">double intervalSize = (parent.max [c] - parent.min [c]) / m_value;
      subspaces [newIndex].min [c] = parent.min [c] + indices [c] * intervalSize;
      subspaces [newIndex].max [c] = parent.min [c] + (indices [c] + class="num">1) * intervalSize;
    }
    class=class="str">"cmt">// Move on to the next subspace
    class="type">int c = coords - class="num">1;
    class="kw">while (c >= class="num">0)
    {
      indices [c]++;
      if (indices [c] < m_value) break;
      indices [c] = class="num">0;
      c--;
    }
    class=class="str">"cmt">// If the full loop completed, exit
    if (c < class="num">0) break;
    newIndex++;
  }
}
class="type">void C_AO_FBA::GenerateNewPopulation()
{
  class=class="str">"cmt">// Calculate the sum of the ranks of all subspaces
  class="type">class="kw">double totalRank = class="num">0.0;
  for (class="type">int i = class="num">0; i < ArraySize(subspaces); i++)
  {
    totalRank += subspaces [i].promisingRank;
  }
  class=class="str">"cmt">// If all ranks are class="num">0, set the uniform distribution
  if (totalRank <= class="num">0.0001) class=class="str">"cmt">// Check for approximate equality to zero
  {
    for (class="type">int i = class="num">0; i < ArraySize(subspaces); i++)
    {
      subspaces [i].promisingRank = class="num">1.0;
    }
    totalRank = ArraySize(subspaces);
  }
  class="type">int points = class="num">0;
  for (class="type">int i = class="num">0; i < ArraySize(subspaces) && points < popSize; i++)
  {
把网格前景评估交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到分形密度热力,你只需决定何时重算子空间排名。

常见问题

原文取最优 60% 作有前景点、前 30% 方格为前景区;P1 过低会丢失多样性,过高则细化不足,连续问题上可先在 50%–70% 区间做网格测试。
步长宜随迭代代数衰减,早期覆盖未知区、后期避免跳出已锁定前景子空间,可用当前子空间边长的分数倍作为上限。
小布内置的 AIGC 看盘页支持把品种波动结构映射成搜索空间示意,你导入适应度函数后可用类似分形细分逻辑缩参,不必手搓寻优循环。
FBA 每代要重排名子空间并递归细分,单代开销略高,但收敛代数少;外汇贵金属波动高噪,建议用代理模型减评估次数再上实盘验证,相关风险自负。