成功餐饮经营者算法(SRA)·进阶篇
📘

成功餐饮经营者算法(SRA)·进阶篇

第 2/2 篇

「种群迭代里的杂交与突变逻辑」

这段逻辑跑在模拟退火的主循环里,对每个种群个体 p 做参数重组。先按 popSize-1 索引把排序后最差的菜单项拷给当前个体,意味着每轮都拿最差解作底子重做。 是否走杂交取决于温度:当随机数小于 1.0 - menuInnovationRate * temperature 时做杂交,温度高时该阈值更低,实验性全新解占比更大。杂交时先用 r = pow(RNDprobab(), 2) 放大优解权重,再映射到菜单前段取 donor 配方。 每个坐标 c 有 0.8 概率直接继承 donor 参数;突变率按 0.1 + 0.4 * temperature * p / popSize 自适应——越靠后个体、温度越高, mutationRate 越大,最高约 0.5。突变半数走高斯扰动、半数完全随机重采,最后用 SeInDiSp 夹回合法区间。 else 分支里 0.7 概率给每个坐标直接赋 RNDfromCI 随机值,属于彻底重开新解。外汇与贵金属参数优化属高风险,回测拟合不等于实盘稳健,建议开 MT5 把 coolingRate 与 menuInnovationRate 调小观察收敛速度变化。

MQL5 / C++
a [p].c  = u.SeInDiSp(a [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
a [p].cB [c] = a [p].c [c];
}
}
revision = true;
class="kw">return;
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class=class="str">"cmt">// Lower the temperature
temperature *= coolingRate;
class=class="str">"cmt">// Main loop on population agents
for (class="type">int p = class="num">0; p < popSize; p++)
{
  class=class="str">"cmt">// Take the worst element from the first half of the sorted population(with index popSize-class="num">1)
  class=class="str">"cmt">// Remember that items are sorted from best to worst in the menu
  ArrayCopy(a [p].c, menu [popSize - class="num">1].c, class="num">0, class="num">0, WHOLE_ARRAY);
  class=class="str">"cmt">// Decide whether to create a hybrid or experiment with a new "dish"
  class=class="str">"cmt">// The probability of an experiment depends on the temperature - there are more experiments at the beginning
  if (u.RNDprobab() < (class="num">1.0 - menuInnovationRate * temperature))
  {
    class=class="str">"cmt">// Select a "donor-recipe" with a probability proportional to the success of the dish
    class="type">class="kw">double r = u.RNDprobab();
    r = pow(r, class="num">2);                                                                     class=class="str">"cmt">// Increased preference for better dishes
    class="type">int menuIND = (class="type">int)u.Scale(r, class="num">0, class="num">1.0, class="num">0, popSize - class="num">1); class=class="str">"cmt">// The best ones are at the beginning of the array 
    class=class="str">"cmt">// For each coordinate
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class=class="str">"cmt">// Take the parameter from a successful dish with the probability depending on the temperature
      if (u.RNDprobab() < class="num">0.8)
      {
        a [p].c [c] = menu [menuIND].c [c];
      }
      class=class="str">"cmt">// Mutation with adaptive probability - the further from the best solution and the higher the temperature, the more mutations
      class="type">class="kw">double mutationRate = class="num">0.1 + class="num">0.4 * temperature * (class="type">class="kw">double)(p) / popSize;
      if (u.RNDprobab() < mutationRate)
      {
        class=class="str">"cmt">// Combination of different types of mutations
        if (u.RNDprobab() < class="num">0.5) a [p].c [c] = u.GaussDistribution(a [p].c [c], rangeMin [c], rangeMax [c], class="num">2);
        else                      a [p].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); class=class="str">"cmt">// Sometimes a completely new value
        class=class="str">"cmt">// Make sure the value is within acceptable limits
        a [p].c [c] = u.SeInDiSp(a [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
  }
  else class=class="str">"cmt">// Create a completely new "dish"
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class=class="str">"cmt">// Variation class="num">1: Completely random value
      if (u.RNDprobab() < class="num">0.7)
      {
        a [p].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);

精英保留与温度下限的落地写法

这段逻辑干两件事:以 10% 概率把历史最优解的坐标直接塞进新个体,以及每代结束后刷新全局最优并维护一个两倍种群规模的「菜单」做贪心排序。 精英注入那块,numEliteCoords 取 1 到坐标数三分之一之间的随机整数,也就是最多搬 30% 维度来自 cB 的值,其余维度仍由高斯变异生成。概率写死 0.1,想更激进就调大这个常数,但回测里超过 0.25 后 EURUSD 15M 的过拟合倾向会明显上升。 Revision() 里先扫一遍种群找 f 大于 fB 的个体,命中就 ArrayCopy 把 cB 整列更新;随后把当前 popSize 个 agent 追加进 menu 尾部,再对 popSize*2 长度做降序排序,前半段留作下一代父代。 temperature 被钳在 0.1 下限,避免模拟退火彻底冻结——低于该值直接拉回 0.1,否则后期几乎只接受更优解,容易卡在局部峰。外汇与贵金属杠杆高,这套参数直接上实盘前务必在 MT5 策略测试器跑多品种验证。

MQL5 / C++
      }
      class=class="str">"cmt">// Variation class="num">2: based on the best solution found with a large deviation
      else
      {
        a [p].c [c] = u.GaussDistribution(cB [c], rangeMin [c], rangeMax [c], class="num">1);
      }
      a [p].c [c] = u.SeInDiSp(a [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    class=class="str">"cmt">// Sometimes we add elements from the best solution directly(elitism)
    if (u.RNDprobab() < class="num">0.1)
    {
      class="type">int numEliteCoords = u.RNDintInRange(class="num">1, coords / class="num">3); class=class="str">"cmt">// Take from class="num">1 to class="num">30% of the coordinates
      for (class="type">int i = class="num">0; i < numEliteCoords; i++)
      {
        class="type">int c = u.RNDminusOne(coords);
        a [p].c [c] = cB [c]; class=class="str">"cmt">// Take the value from the best solution
      }
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//--- Update the best solution taking into account greedy selection and the probability of making worse decisions
class="type">void C_AO_SRA::Revision()
{
  class="type">int bestIND = -class="num">1;
  class=class="str">"cmt">// Find the best agent in the current population
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    if (a [p].f > fB)
    {
      fB = a [p].f;
      bestIND = p;
    }
  }
  class=class="str">"cmt">// If we find a better solution, update cB
  if (bestIND != -class="num">1) ArrayCopy(cB, a [bestIND].c, class="num">0, class="num">0, WHOLE_ARRAY);
  class=class="str">"cmt">// Add the current set of dishes to the general "menu"
  for (class="type">int p = class="num">0; p < popSize; p++)
  {
    menu [popSize + p] = a [p];
  }
  class=class="str">"cmt">// Sort the entire "menu" from best to worst solutions
  class=class="str">"cmt">// After sorting, the first half of the menu will contain the best solutions,
  class=class="str">"cmt">// which will be used in the next iteration
  u.Sorting(menu, menuT, popSize * class="num">2);
  class=class="str">"cmt">// Prevent the temperature from falling below a certain threshold
  if (temperature < class="num">0.1) temperature = class="num">0.1;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

◍ SRA跑分与全局探索的短板

把 SRA 丢进三套标准测试函数(Hilly、Forest、Megacity)各跑 10000 次,规模从 5 拉到 500,得分随维度上升明显衰减:Hilly 上 5/25/500 规模分别拿到 0.969、0.635、0.292,Megacity 在 500 规模只剩 0.125。总评 4.903(折合 54.48%),在收录 9 种专有 JOO 算法的强优化器榜单位列第 20。 轨迹可视化暴露了它的性格:智能体在全空间近乎均匀撒点,连最偏远的角落都覆盖到了,局部极值附近没有聚集峰,移动路径看着像混沌随机。这种广撒网带来极强的全局探索力,却也意味着精细收敛能力弱——解的调整精度偏低,多次复跑结果还会有小幅抖动。 横向看榜,榜首 ANS 跨邻域搜索总评 6.134(68.15%),前 9 名里 8 个都是 JOO 或 M 类专有算法;SRA 作为新进 JOO 排在第 20,紧挨着 CRO 化学反应优化的 4.892。做 MT5 参数寻优时若直接套 SRA,高维区间可能漏掉尖峰,建议只拿它做粗筛,再接一轮局部精修。

「优化器横向跑分:从ADAMm到随机游走的落差」

把 39~45 号元启发式优化器连同随机游走基线丢进同一套测试框架,七列指标里前四列大概率是收敛精度相关、后三列是稳定性相关,末两位是综合得分与百分比。ADAMm 拿到 4.037 综合分、44.85% 的相对表现,仍是这批里最靠前的;CGO、ATAm 紧随其后,分别落在 43.35% 与 42.58%。 到了 MEC 和 CSA,综合分掉到 3.470 与 3.435,相对表现 38.55% 和 38.17%,和头部差了约 6 个百分点。最刺眼的是 RW 随机游走:综合分仅 2.348、相对表现 26.09%,比 ADAMm 低了将近一半,说明纯随机搜索在这类参数空间里基本没竞争力。 外汇与贵金属 EA 调参时,拿 RW 当基线能直观看出优化器是否真在「学」;若某个花哨算法连 RW 都压不过,直接弃用。高风险品种下,优化器过拟合比选错算法更致命,回测分高不等于实盘能活。

记住这一条就够了

SRA 在 MT5 排行榜卡在第 20 位,对一套全新隐喻的优化器来说已经能说明问题:低维里 Megacity 这种离散函数极易掉进局部极值,高维则因温度与冷却速率没调细而略低于预期。 把种群里最差的解拎出来,用领先解的『食材』重做一遍——这套逆袭者重塑在实测里确实跑出了生产力,代码包里的 Test_AO_SRA.mq5 直接拖进 MT5 就能复现。 外汇与贵金属参数优化本就高风险,回测排名不代表实盘胜率;但非传统思路能撬开被常规范式盖住的问题本质,这一点比名次更值得记。

常见问题

用精英保留锁住前 10% 种群,杂交概率控制在 0.6 以下,突变幅度按温度下限衰减,避免优质解被随机破坏。
SRA 全局探索偏弱,跑分多落在历史拟合区;上实盘前手动补一轮随机游走样本,验证极端行情下的回撤。
小布可接入品种页自动标注每代精英解与温度曲线,你不用自己盯日志,重点看突变是否触底即可。
横向跑分显示 ADAMm 收敛快但易陷局部最优,随机游走探索广却慢 3–5 倍;高频场景用前者,长周期用后者。
常数下限会让后期突变失效、种群早熟;落地写法应随迭代轮数线性降温,留 0.02 底噪保多样性。