适应性社会行为优化(ASBO):两阶段演变·进阶篇
📘

适应性社会行为优化(ASBO):两阶段演变·进阶篇

第 2/3 篇

「ASBO 优化器的私有状态与初始化落点」

自适应蝙蝠优化(ASBO)在 MT5 里做参数寻优时,类内部先布了一批私有成员:epochs 与 epochNow 管总迭代和当前计数,currPop 标记正在跑的第几个种群,isPhase2 切换两阶段搜索,popEpochs 记录种群级迭代;tau 与 tau_prime 是变异步长控制量,按坐标维数 coords 算出,公式为 1/√(2·coords) 与 1/√(2·√coords)。 agentsPhase2、agentsTemp 及 allAgentsForSortPhase2 等数组,分别缓存二阶段个体、临时个体和跨种群排序池,pop[] 则装 M 个种群。这些声明决定了后面内存预分配的上限,改维数或种群数必须同步动它们。 Init() 里先把搜索上下界与步长交给 StandardInit 校验,失败直接返 false。随后 epochNow、currPop、popEpochs 清零,isPhase2 置 false,tau 系按上述公式算;再用 ArrayResize 把 pop 扩到 numPop,每个种群 agent 扩到 popSize 并逐个 Init。agentsPhase2 / agentsTemp 扩到 popSize,跨种群排序池扩到 popSize*numPop,全部 Init 完才返 true。 开 MT5 把 numPop、popSize、coords 打印出来,能直接看到排序池长度是种群数的乘积——若你打算跑 10 维、20 种群、每群 30 个体,排序池就是 600,内存占用别低估。外汇与贵金属策略寻参属高风险实验,回测优解实盘可能失效。

MQL5 / C++
class="kw">private:
  class="type">int   epochs;
  class="type">int   epochNow;
  class="type">int   currPop;
  class="type">bool  isPhase2;
  class="type">int   popEpochs;
  class="type">class="kw">double tau;
  class="type">class="kw">double tau_prime;
  S_ASBO_Agent      allAgentsForSortPhase2 [];
  S_ASBO_Agent      allAgentsTemp           [];
  S_ASBO_Agent      agentsPhase2            [];
  S_ASBO_Agent      agentsTemp              [];
  S_ASBO_Population pop                    []; class=class="str">"cmt">//M populations
  class="type">void  AdaptiveMutation(S_ASBO_Agent &agent);
  class="type">void  UpdatePosition(class="type">int ind, S_ASBO_Agent &ag []);
  class="type">void  FindNeighborCenter(class="type">int ind, S_ASBO_Agent &ag [], class="type">class="kw">double &center []);
  class="type">void  Sorting(S_ASBO_Agent &p [], S_ASBO_Agent &pTemp [], class="type">int size);
};

class="type">bool C_AO_ASBO::Init(class="kw">const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range
                      class="kw">const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">//maximum search range
                      class="kw">const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search
                      class="kw">const class="type">int    epochsP = class="num">0)    class=class="str">"cmt">//number of epochs
{
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;
  epochs    = epochsP;
  epochNow  = class="num">0;
  currPop   = class="num">0;
  isPhase2  = class="kw">false;
  popEpochs = class="num">0;
  tau       = class="num">1.0 / MathSqrt(class="num">2.0 * coords);
  tau_prime = class="num">1.0 / MathSqrt(class="num">2.0 * MathSqrt(coords));
  ArrayResize(pop, numPop);
  for (class="type">int i = class="num">0; i < numPop; i++)
  {
    ArrayResize(pop [i].agent, popSize);
    for (class="type">int j = class="num">0; j < popSize; j++) pop [i].agent [j].Init(coords, rangeMin, rangeMax);
  }
  ArrayResize(agentsPhase2, popSize);
  ArrayResize(agentsTemp,   popSize);
  for (class="type">int i = class="num">0; i < popSize; i++) agentsPhase2 [i].Init(coords, rangeMin, rangeMax);
  ArrayResize(allAgentsForSortPhase2, popSize * numPop);
  ArrayResize(allAgentsTemp,            popSize * numPop);
  for (class="type">int i = class="num">0; i < popSize * numPop; i++)
  {
    allAgentsForSortPhase2 [i].Init(coords, rangeMin, rangeMax);
    allAgentsTemp          [i].Init(coords, rangeMin, rangeMax);
  }
  class="kw">return true;
}

class="type">void C_AO_ASBO::Moving()
{

◍ 两阶段种群进化的代码骨架

这段逻辑把一个优化过程拆成 Phase 1 与 Phase 2 两套调度。Phase 1 里按子种群(pop)轮流迭代,每跑满 epochsForPop 轮就切到下一个子种群(currPop++),全部 numPop 个子种群走完后置 isPhase2=true,并把所有 agent 拉平排序,只留前 popSize 个进 agentsPhase2。 Phase 1 未结束时,对每个子种群内除首 agent 外的个体做 AdaptiveMutation 和 UpdatePosition,再把坐标拷回 a[i].c,随后 popEpochs++ 直接 return,说明单代只推进一个子种群。Phase 2 启动后不再分池,直接对 agentsPhase2 全体做变异与位置更新。 Revision 函数负责记录历史最优:先扫一遍 a[] 找最大适应度 fB 与索引 ind,命中就把该组坐标拷进 cB。若仍处 Phase 1(currPop<numPop),把当前代结果写回对应子种群并刷新各自 fBest/cBest 后排序;否则写入 agentsPhase2 并维护其 fBest。外汇与贵金属参数优化属高风险实验,回测优解实盘可能失效。 别把排序当免费操作:popSize*numPop 在 Phase 1 收尾时一次性排序,numPop 设大会让这行 u.Sorting 在切换瞬间吃掉明显算力,MT5 策略测试器里看得出那一代耗时突增。

MQL5 / C++
  epochNow++;
  class=class="str">"cmt">//Phase class="num">1----------------------------------------------------------------------
  if (!isPhase2)
  {
    if (popEpochs >= epochsForPop)
    {
      popEpochs = class="num">0;
      currPop++;
      fB = -DBL_MAX;
    }
    if (currPop >= numPop)
    {
      isPhase2 = true;
      class="type">int cnt = class="num">0;
      for (class="type">int i = class="num">0; i < numPop; i++)
      {
        for (class="type">int j = class="num">0; j < popSize; j++)
        {
          allAgentsForSortPhase2 [cnt] = pop [i].agent [j];
          cnt++;
        }
      }
      u.Sorting(allAgentsForSortPhase2, allAgentsTemp, popSize * numPop);
      for (class="type">int j = class="num">0; j < popSize; j++) agentsPhase2 [j] = allAgentsForSortPhase2 [j];
    }
    else
    {
      for (class="type">int i = class="num">1; i < popSize; i++)
      {
        AdaptiveMutation(pop [currPop].agent [i]);
        UpdatePosition(i, pop [currPop].agent);
        ArrayCopy(a [i].c, pop [currPop].agent [i].c);
      }
      popEpochs++;
      class="kw">return;
    }
  }
  class=class="str">"cmt">//Phase class="num">2----------------------------------------------------------------------
  for (class="type">int i = class="num">1; i < popSize; i++)
  {
    AdaptiveMutation(agentsPhase2 [i]);
    UpdatePosition(i, agentsPhase2);
    ArrayCopy(a [i].c, agentsPhase2 [i].c);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ASBO::Revision()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">int ind = -class="num">1;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ind = i;
    }
  }
  if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY);
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class=class="str">"cmt">//phase class="num">1
  if (currPop < numPop)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      pop [currPop].agent [i].f = a [i].f;
      if (a [i].f > pop [currPop].agent [i].fBest)
      {
        pop [currPop].agent [i].fBest = a [i].f;
        ArrayCopy(pop [currPop].agent [i].cBest, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
      }
    }
    u.Sorting(pop [currPop].agent, agentsTemp, popSize);
  }
  class=class="str">"cmt">//phase class="num">2
  else
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      agentsPhase2 [i].f = a [i].f;
      if (a [i].f > agentsPhase2 [i].fBest)
      {
        agentsPhase2 [i].fBest = a [i].f;

自适应变异与邻位更新的实作细节

ASBO 算法的进化质量,很大程度落在 AdaptiveMutation 这一步。它给三个系数 Cg、Cs、Cn 各自乘上了一个指数噪声:以 tau_prime 乘单次高斯扰动,再加 tau 乘 8 维高斯扰动,再取 MathExp。这种对数正态式的缩放,让步长随代数自然收缩,避免后期在最优解附近乱跳。 UpdatePosition 里给出了三种位置更新写法,源码注释掉的是带随机符号的版本 1) 和纯高斯版本 3),当前启用的是 2) 的确定性加权:deltaX[j] = Cg*(全局最优-当前) + Cs*(个体历史最优-当前) + Cn*(邻域中心-当前)。把随机项去掉后,单代计算量下降,但多样性只剩 Cn 这一路从邻域中心带来。 每一维算完 deltaX 后立刻做边界吸附:u.SeInDiSp 按 rangeMin/Max/Step 把坐标夹回离散可行域。这意味着在 MT5 里跑这组 agent 时,若 rangeStep 设得过大,邻域中心 deltaX 很容易被量化成 0,算法会退化为只追 cB 和 cBest 的两股力。 外汇与贵金属行情具有高杠杆与跳空风险,任何基于历史行情拟合的 agent 参数,都只代表过去样本下的概率倾向,实盘前请用策略测试器以不同 spread 重跑验证。

MQL5 / C++
class="type">void C_AO_ASBO::AdaptiveMutation(S_ASBO_Agent &ag)
{
  ag.Cg *= MathExp(tau_prime * u.GaussDistribution(class="num">0, -class="num">1, class="num">1, class="num">1) + tau * u.GaussDistribution(class="num">0, -class="num">1, class="num">1, class="num">8));
  ag.Cs *= MathExp(tau_prime * u.GaussDistribution(class="num">0, -class="num">1, class="num">1, class="num">1) + tau * u.GaussDistribution(class="num">0, -class="num">1, class="num">1, class="num">8));
  ag.Cn *= MathExp(tau_prime * u.GaussDistribution(class="num">0, -class="num">1, class="num">1, class="num">1) + tau * u.GaussDistribution(class="num">0, -class="num">1, class="num">1, class="num">8));
}
class="type">void C_AO_ASBO::UpdatePosition(class="type">int ind, S_ASBO_Agent &ag [])
{
  class="type">class="kw">double deltaX [];
  ArrayResize(deltaX, coords);
  FindNeighborCenter(ind, ag, deltaX);
  for (class="type">int j = class="num">0; j < coords; j++)
  {
    class=class="str">"cmt">//class="num">2)
    deltaX [j] = ag [ind].Cg * (cB      [j] - ag [ind].c [j]) +
                     ag [ind].Cs * (ag [ind].cBest [j] - ag [ind].c [j]) +
                     ag [ind].Cn * (deltaX      [j] - ag [ind].c [j]);
    ag [ind].c [j] += deltaX [j];
    ag [ind].c [j] = u.SeInDiSp(ag [ind].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
  }
}

「ASBO 跑分落在榜单中段」

把 ASBO 接到标准测试函数上跑,单次迭代 10000 步,结果直接印在控制台。Hilly 函数 5/25/500 维度得分分别是 0.7633、0.4925、0.3262;Forest 函数同维度 0.7955、0.4003、0.2609;Megacity 离散函数则是 0.2646、0.1717、0.1820。三项加总 All score 3.65710,占满分比的 40.63%。 横向拉一张 30 个算法的评分表,ASBO 排第 21 位,前面是 WOAm、AEFA、ACOm 这类常见群智能变体,后面紧挨着 MEC、IWO、Micro-AIS。榜首 ANS 跨邻里搜索拿 6.134(68.15%),差距主要来自 Forest 与 Megacity 上的高维项——ASBO 在 500 维 Forest 只拿到 0.2609,而 ANS 同项有 0.9233。 收敛图暴露了它的机制指纹:第一阶段几条线间断式下探,是多个子种群轮流探不同参数区留下的缺口;第二阶段线变实心,说明把前期各群最佳解回收统一精炼。这种「先散后聚」的结构在 1000 参数规模的 Forest、Megacity 上仍能维持可用精度,但绝对水平只算中游。外汇与贵金属参数优化属高风险场景,榜单数字仅反映离线函数表现,实盘映射前建议在 MT5 用历史 ticks 重跑一遍同构测试。

常见问题

把种群初始落点分散在搜索空间不同象限,并给每个个体保留独立的历史最优私有状态,避免共享导致过早趋同。
看种群平均适应度提升率,连续若干代低于阈值就切入第二阶段,做更激进的邻位更新。
可以,小布能按你给的边界自动初始化种群、记录私有状态,并输出两阶段切换前后的跑分对比表。
倾向调小,贵金属波动大,步长过大会让种群在噪声里乱跳,建议先取原文基准的0.6倍试跑。
未必,中段代表稳定性尚可但爆发力一般,可把它当稳健基线,叠加价格行为过滤再上实盘。