无政府社会优化(ASO)算法·进阶篇
(2/3)· 继社会群体演化之后,这篇进阶拆解 ASO 如何在 MetaTrader 5 里避开局部最优陷阱
◍ 粒子群变体的参数映射与结构体字段
这段声明把一套类粒子群(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 表示由外部控制迭代轮数。
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 过高而发散——属高风险调参。
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 可能加快收敛,但外汇与贵金属市场高风险,过冲会导致参数组合在样本外失效。
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](上一步位)。这意味着在每一维坐标上,历史最优与上一步被等概率切换。
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]; }
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]; }