无政府社会优化(ASO)算法·进阶篇
🜂

无政府社会优化(ASO)算法·进阶篇

(2/3)· 继社会群体演化之后,这篇进阶拆解 ASO 如何在 MetaTrader 5 里避开局部最优陷阱

实战向进阶 第 2/3 篇
把 PSO 直接搬进 EA 的人常卡在早熟收敛——种群挤在一个山头就不动了。ASO 用一群故意乱跑、专挑最差位置试探的个体破局,这套机制值得你先看懂再写代码。

◍ 粒子群变体的参数映射与结构体字段

这段声明把一套类粒子群(ASO)算法的 8 个可调参数先塞进 params 数组,再在 SetParams 里回写进类成员变量,MT5 里改 EA 输入项时直接动数组索引 0~7 即可。 params[0] 到 params[7] 依次对应 popSize(种群规模,整型强转)、anarchyProb(无政府行为概率)、omega(惯性权重)、lambda1 / lambda2(个体最优与全局最优的加速系数)、alpha / theta / delta(FI、EI、II 三个指标计算用的标量)。popSize 是唯一做 (int) 强制转换的字段,其余 7 个都是 double 直读。 类里还挂了 member[] 向量存社会成员,CalculateFI / CalculateEI / CalculateII 三个私有方法各吃一个 memberIndex 返回 double,说明适应度不是直接算,而是拆成三种指标再合成。外汇与贵金属市场波动剧烈、杠杆风险高,这类元启发式参数若直接上实盘,需先在策略测试器用历史数据验证收敛稳定性。 下面这段是原始声明节选,逐行对应上面说的映射关系:params[0].name 写 "popSize" 并赋值 popSize 变量;params[1] 绑定 anarchyProb;params[2] 绑 omega;params[3]、[4] 分别绑 lambda1、lambda2;[5]~[7] 绑 alpha、theta、delta。SetParams 里则是反向把 params[].val 读回同名成员,epochsP 默认 0 表示由外部控制迭代轮数。

MQL5 / C++
params [class="num">0].name = "popSize";      params [class="num">0].val = popSize;
params [class="num">1].name = "anarchyProb"; params [class="num">1].val = anarchyProb;
params [class="num">2].name = "omega";       params [class="num">2].val = omega;
params [class="num">3].name = "lambda1";     params [class="num">3].val = lambda1;
params [class="num">4].name = "lambda2";     params [class="num">4].val = lambda2;
params [class="num">5].name = "alpha";       params [class="num">5].val = alpha;
params [class="num">6].name = "theta";       params [class="num">6].val = theta;
params [class="num">7].name = "delta";       params [class="num">7].val = delta;
}
class="type">void SetParams()
{
  popSize     = (class="type">int)params [class="num">0].val;
  anarchyProb = params      [class="num">1].val;
  omega       = params      [class="num">2].val;
  lambda1     = params      [class="num">3].val;
  lambda2     = params      [class="num">4].val;
  alpha       = params      [class="num">5].val;
  theta       = params      [class="num">6].val;
  delta       = params      [class="num">7].val;
}
class="type">bool Init(class="kw">const class="type">class="kw">double &rangeMinP  [],
           class="kw">const class="type">class="kw">double &rangeMaxP  [],
           class="kw">const class="type">class="kw">double &rangeStepP [],
           class="kw">const class="type">int     epochsP = class="num">0);
class="type">void Moving();
class="type">void Revision();
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double anarchyProb; class=class="str">"cmt">// Probability of anarchic behavior
class="type">class="kw">double omega;      class=class="str">"cmt">// Inertia weight
class="type">class="kw">double lambda1;    class=class="str">"cmt">// Acceleration coefficient for P-best
class="type">class="kw">double lambda2;    class=class="str">"cmt">// Acceleration coefficient for G-best
class="type">class="kw">double alpha;      class=class="str">"cmt">// Parameter for FI calculation
class="type">class="kw">double theta;      class=class="str">"cmt">// Parameter for EI calculation
class="type">class="kw">double delta;      class=class="str">"cmt">// Parameter for II calculation
S_ASO_Member member []; class=class="str">"cmt">// Vector of society members
class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
class="type">class="kw">double CalculateFI(class="type">int memberIndex);
class="type">class="kw">double CalculateEI(class="type">int memberIndex);
class="type">class="kw">double CalculateII(class="type">int memberIndex);

「原子优化里的个体位移逻辑」

这段 C_AO_ASO 类的实现,把一类基于「原子社会行为」的优化算法搬进了 MT5 框架。核心是两个方法:Init 负责按传入的区间与步长数组初始化种群,Moving 负责每一代个体的坐标迭代。 Init 里先调 StandardInit 做通用校验,再用 ArrayResize(member, popSize) 把种群容器撑到 popSize 大小,随后每个 member[i] 按 coords 维度 Init。注意 popSize 与 coords 来自类内成员,调用前必须保证 rangeMin/Max/Step 三个数组长度等于 coords,否则 StandardInit 会返回 false。 Moving 的第一次调用(revision 为 false)是「冷启动」:给所有个体在每个维度上从区间均匀随机取值,再用 SeInDiSp 吸附到离散步长网格,并把当前坐标写进 pPrev。之后 revision 置 true,后续迭代才进入真正的移动分支。 正式迭代里,每个个体先算 fi(善变指数)、ei(外部不规则度)、ii(内部不规则度),三者都是 0.0 起的 double。移动决策靠 u.RNDprobab() 两次随机:若第一次随机值小于 anarchyProb 直接全局乱跳;否则用第二次随机 rnd 跟 fi/ei/ii 比较,分别走 CurrentMP(当前记忆)、SocietyMP(社会坐标)、PastMP(历史坐标)三种局部更新。这套分支让种群在「探索」与「利用」间按概率切换,外汇与贵金属参数寻优时容易跳出局部极值,但同样可能因 anarchyProb 过高而发散——属高风险调参。

MQL5 / C++
class="type">void  CurrentMP(S_AO_Agent &agent, S_ASO_Member &memb, class="type">int coordInd);
class="type">void  SocietyMP(S_AO_Agent &agent, class="type">int coordInd);
class="type">void  PastMP(S_AO_Agent &agent, S_ASO_Member &memb, class="type">int coordInd);
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_ASO::Init(class="kw">const class="type">class="kw">double &rangeMinP  [],
                     class="kw">const class="type">class="kw">double &rangeMaxP  [],
                     class="kw">const class="type">class="kw">double &rangeStepP [],
                     class="kw">const class="type">int     epochsP = class="num">0)
{
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  ArrayResize(member, popSize);
  for (class="type">int i = class="num">0; i < popSize; i++) member [i].Init(coords);
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ASO::Moving()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (!revision)
  {
    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]);
        member [i].pPrev [c] = a [i].c [c];
      }
    }
    revision = true;
    class="kw">return;
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">class="kw">double fi  = class="num">0.0; class=class="str">"cmt">//fickleness index
  class="type">class="kw">double ei  = class="num">0.0; class=class="str">"cmt">//external irregularity index
  class="type">class="kw">double ii  = class="num">0.0; class=class="str">"cmt">//internal irregularity index
  class="type">class="kw">double rnd = class="num">0.0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    fi = CalculateFI(i);
    ei = CalculateEI(i);
    ii = CalculateII(i);
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      member [i].pPrev [c] = a [i].c [c];
      rnd = u.RNDprobab();
      if (u.RNDprobab() < anarchyProb) a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
      else
      {
        if (rnd > fi) CurrentMP(a [i], member [i], c);
        else
        {
          if (rnd < ei) SocietyMP(a [i], c);
          else
          {
            if (rnd < ii) PastMP(a [i], member [i], c);
          }
        }
      }
    }
  }

适应度更新与三层交互算子的实现细节

Revision() 负责每代重置全局最优与个体历史最优。它遍历 popSize 个代理,若当前适应度 a[i].f 大于 fB 就记下索引 ind,同时把优于自身历史最优的解通过 ArrayCopy 写入 pBest,最后若 ind 有效则将 cB 同步为全局最优坐标。 CalculateFI、CalculateEI、CalculateII 分别给出跟随、探索、内省三类权重。FI 用 1 - alpha*(pBestFitness - currentFitness)/(globalBestFitness - currentFitness) 表达向个人与全局最优的牵引强度;EI 与 II 均用 1 - MathExp(-差值/(基准*theta或delta)) 形式,指数衰减让远离最优者获得更高随机扰动概率。 CurrentMP 是核心位置更新:velocity = omega*(agent.c - pBest) + lambda1*r1*(pBset - agent.c) + lambda2*r2*(cB - agent.c),其中 r1、r2 由 u.RNDprobab() 给到 [0,1) 随机值,omega、lambda1、lambda2 为可调惯性/社会/认知系数,最后 agent.c[coordInd] += velocity 完成单维位移。 SocietyMP 提供社会层突变:随机挑一个其他成员 otherMember = u.RNDminusOne(popSize),以 0.5 概率直接跳到全局最优 cB[coordInd],否则跳到该成员历史最优 pBest[coordInd]。在 MT5 里把 lambda2 从 1.0 提到 1.5 可能加快收敛,但外汇与贵金属市场高风险,过冲会导致参数组合在样本外失效。

MQL5 / C++
for (class="type">int c = class="num">0; c < coords; c++)
{
  a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ASO::Revision()
{
  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 (a [i].f > member [i].pBestFitness)
    {
      member [i].pBestFitness = a [i].f;
      ArrayCopy(member [i].pBest, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
  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">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_ASO::CalculateFI(class="type">int memberIndex)
{
  class="type">class="kw">double currentFitness      = a      [memberIndex].f;
  class="type">class="kw">double personalBestFitness = member [memberIndex].pBestFitness;
  class="type">class="kw">double globalBestFitness   = fB;
  class=class="str">"cmt">//class="num">1 - class="num">0.9 * (class="num">800-x)/(class="num">1000-x)
  class="kw">return class="num">1 - alpha * (personalBestFitness - currentFitness) / (globalBestFitness - currentFitness);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_ASO::CalculateEI(class="type">int memberIndex)
{
  class="type">class="kw">double currentFitness   = a [memberIndex].f;
  class="type">class="kw">double globalBestFitness = fB;
  class=class="str">"cmt">//class="num">1-exp(-(class="num">10000-x)/(class="num">10000*class="num">0.9))
  class="kw">return class="num">1 - MathExp(-(globalBestFitness - currentFitness) / (globalBestFitness * theta));
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_ASO::CalculateII(class="type">int memberIndex)
{
  class="type">class="kw">double currentFitness      = a      [memberIndex].f;
  class="type">class="kw">double personalBestFitness = member [memberIndex].pBestFitness;
  class=class="str">"cmt">//class="num">1-exp(-(class="num">10000-x)/(class="num">10000*class="num">0.9))
  class="kw">return class="num">1 - MathExp(-(personalBestFitness - currentFitness) / (personalBestFitness * delta));
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ASO::CurrentMP(S_AO_Agent &agent, S_ASO_Member &memb, class="type">int coordInd)
{
  class="type">class="kw">double r1 = u.RNDprobab();
  class="type">class="kw">double r2 = u.RNDprobab();
  class="type">class="kw">double velocity = omega      *         (agent.c      [coordInd] - memb.pBest [coordInd]) +
                                      lambda1 * r1 * (memb.pBest [coordInd] - agent.c      [coordInd]) +
                                      lambda2 * r2 * (cB          [coordInd] - agent.c      [coordInd]);
  agent.c [coordInd] += velocity;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ASO::SocietyMP(S_AO_Agent &agent, class="type">int coordInd)
{
  class="type">int otherMember = u.RNDminusOne(popSize);
  agent.c [coordInd] = u.RNDprobab() < class="num">0.5 ? cB [coordInd] : member [otherMember].pBest [coordInd];
}

◍ 历史记忆的随机回灌

在 AO-ASO 混合优化器里,PastMP 负责把个体历史信息回写进当前智能体的坐标。它不按固定规则取最优或上一步,而是用一次随机判决决定记忆来源。 代码中的判定阈值是 0.5:u.RNDprobab() 生成 [0,1) 均匀随机数,小于 0.5 时取 memb.pBest[coordInd](历史最优位),否则取 memb.pPrev[coordInd](上一步位)。这意味着在每一维坐标上,历史最优与上一步被等概率切换。

MQL5 / C++
class="type">void C_AO_ASO::PastMP(S_AO_Agent &agent, S_ASO_Member &memb, class="type">int coordInd)
{
  agent.c [coordInd] = u.RNDprobab() < class="num">0.5 ? memb.pBest [coordInd] : memb.pPrev [coordInd];
}
逐行拆解: 第1行 定义方法 PastMP,接收智能体引用、成员引用与坐标索引。 第2行 函数体开始。 第3行 用三元表达式按 0.5 概率从 pBest 或 pPrev 选值赋给 agent.c[coordInd],完成该维的历史回灌。 第4行 函数结束。 在 MT5 里把 0.5 改成 0.7,会让历史最优位被选中的概率升到 70%,可能加快收敛但也可能陷入局部最优;外汇与贵金属参数优化属高风险实验,建议先用历史数据观察分布再上实盘。

MQL5 / C++
class="type">void C_AO_ASO::PastMP(S_AO_Agent &agent, S_ASO_Member &memb, class="type">int coordInd)
{
  agent.c [coordInd] = u.RNDprobab() < class="num">0.5 ? memb.pBest [coordInd] : memb.pPrev [coordInd];
}
把参数敏感性测试交给小布
ASO 里无政府程度随处境差异浮动,手调容易漏掉边界组合。小布盯盘已内置多参数扫描视图,打开对应品种页即可批量跑回测,你只管看哪组更抗局部最优。

常见问题

PSO 靠个体与群体最优牵引,容易早熟;ASO 引入无政府个体,探索阶段主动移向曾到过的最差位置,用非理性扰动换更广的搜索覆盖,概率上更不易陷局部最优。
没有固定值,连续问题常从 30–80 起调;种群太小探索弱,太大拖慢回测。建议结合品种周期先用小样本粗扫再缩范围。
目前小布提供参数扫描与诊断视图,不内嵌具体 ASO 源码;你可以把自拟 EA 的回测结果贴进对应品种页,让 AIGC 帮你看哪段参数敏感。
这是算法刻意的反直觉设计:最差位置标记了已知坏区边界,无政府个体借非理性跳跃刺探未知,从而扩大解空间覆盖,属于探索优先的权衡。