最负盛名的人工协作搜索算法的改进版本(AXSm)(基础篇)
「AXSm 协作搜索在 MT5 里的落地与回测表现」
AXSm 是经典人工协作搜索算法的一个改进版本,主要面向 MetaTrader 5 平台做策略寻优。它把多组候选解放到同一搜索空间里互相交换信息,再用适应度函数筛选,比单线程穷举更省算力。 作者 Andrey Dik 在 2024 年 12 月 3 日发布的示例里,该算法模块在 MT5 测试环境中累计被社区查看 801 次,说明这类协作式优化在零售交易者圈子里已有一定关注度。 实际跑下来,算法的价值不在「自动盈利」,而在把参数平原上的无效区域快速剔除。外汇与贵金属杠杆品种波动剧烈、滑点随机,用它做 EA 入参预筛时仍要人工复核样本外表现,过拟合概率不低。
从基础 ACS 到增强版演化
上一篇文章里我们拆过基础版人工协作搜索(ACS),它借自然界捕食者与猎物的互利关系去解多维优化问题。核心逻辑是让两类智能体在搜索空间里互相牵引,逼近全局最优。 现在要往前走一步:把 ACS 的基础框架接上受生态系统观察启发的附加机制,做成改进版本。这类增强不改动“协作互利”的根本,只是在搜索策略层加约束或激励。 已知改进版 ACS 在复杂函数寻优上,迭代收敛步数相对基础版有概率性缩减,具体幅度取决于附加机制的设计。外汇/贵金属参数优化属高风险实验,回测结论不能直接外推到实盘。 下一步我们会直接把改进版代码挂进 MT5 策略测试器,用 EURUSD 的 H1 历史数据跑一遍,看寻优耗时和稳定度到底差多少。
◍ ACS三次修改的MT5类实现拆解
改进型人工协作搜索(ACS)在 MT5 里落地,作者做了三轮修改而非一次性重写。第一次小改给 A、B 矩阵加了一列存函数值,并把 M 矩阵的初始化从全 0 再随机置 1,改成直接填充后按 bioProbab 概率翻转,测试函数上精度有微幅提升;但工程上没沿用附加列,而是用智能体结构里的 f 字段记适应度。 第二次改动了候选者筛选逻辑:增强后的 A、B 每行先按 f 排序,逐行比对后,f 高的进 Predator、低的进 Prey,不再是随机挑。主循环末尾用随机比较更新 A、B,两种群各 50% 概率被改写,多样性比原版好。 第三次直接并池:新建 Pop 合并 A、B 全部行,按 f 排序后前 popSize 行为捕食者、后 popSize 行为猎物,迭代中优先保最优解,收敛更快。C_AO_ACSm3 的 Init 给 Pop、pTemp 分配了 popSize*2 空间,phase 分段是为顺次算适应度被迫加的。 C_AO_ACSm1 的 Moving 用 phase 0/1/2 分别搬坐标、回写 A 的 f、回写 B 的 f,phase>=3 才进 Selection:u.RNDprobab()<0.5 时整组 A 做 Predator(Key=1)否则 B 做 Predator(Key=2),Prey 同理;随后 ArrayShuffle 只打乱每个个体自身坐标。Revision 里若 a[i].f 优于 Predator[i].f 就覆盖,再按 Key 写回 A 或 B,并扫出 Ybest/Ibest 更新全局 fB。 ArrayShuffle 三版通用:从末位逆序,对每个 i 取 [0,i] 随机 j 做交换。第二次 Moving 的 Selection 改成逐个体比 A[i].f 与 B[i].f,大者入 Predator、小者入 Prey;变异公式 a[i].c[j] = Predator[i].c[j] + R*(Prey[i].c[j]-Predator[i].c[j]),M 初始填 0 再按概率置 1 触发交叉。 开 MT5 把 C_AO_ACSm1 到 m3 的 popSize 设 30、bioProbab 设 0.3 跑同一测试函数,重点看 m3 的 Pop 排序后捕食者半区 f 均值是否稳定高于 m1 随机选。代码层注意 m2 的 M 矩阵初始化与 m1 相反,直接抄容易出静默 bug。
「ACS 算法的类骨架与初始化入口」
把人工协同搜索(Artificial Cooperative Search)塞进 MT5 的优化器,最省事的办法是继承一个现成的 C_AO 基类,再写个派生类把种群规模和生物交互概率暴露成可调参数。下面这段类声明里,popSize 默认给 1,bioProbab 默认 0.9,意味着单一个体以九成概率参与生物交互——这种极简配置在 M1 周期调参时跑起来很快,但容易陷局部最优。 构造函数里顺手把参数名和值写进 params 数组,外部面板就能直接拖滑块。SetParams() 负责把面板值回写成员变量,Init() 则接管搜索区间与步长,内部用 StandardInit 做范围校验,再给 A、B、Predator、Prey、M 五组智能体数组按 popSize 扩容。 别把种群设太大当万能药 popSize 从 1 调到 30 时,回测耗时可能翻 20 倍以上,而 EURUSD M1 上参数平原往往不深,小种群反而更利于快速扫形。外汇与贵金属杠杆高、滑点跳空频繁,任何优化结果都只是历史概率,上线前务必用 MT5 策略测试器跑至少 3 个月 Tick 数据复核。
class C_AO_ACSm1 : class="kw">public C_AO { class="kw">public: class=class="str">"cmt">//-------------------------------------------------------------------- ~C_AO_ACSm1() { } C_AO_ACSm1() { ao_name = "ACS"; ao_desc = "Artificial Cooperative Search m1"; ao_link = "[MQL5官方文档] popSize = class="num">1; class=class="str">"cmt">//population size bioProbab = class="num">0.9; class=class="str">"cmt">//biological interaction probability ArrayResize(params, class="num">2); params [class="num">0].name = "popSize"; params [class="num">0].val = popSize; params [class="num">1].name = "bioProbab"; params [class="num">1].val = bioProbab; } class="type">void SetParams() { popSize = (class="type">int)params [class="num">0].val; bioProbab = params [class="num">1].val; } class="type">bool 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 class="type">void Moving(); class="type">void Revision(); class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">class="kw">double bioProbab; class=class="str">"cmt">//biological interaction probability class="kw">private: class=class="str">"cmt">//------------------------------------------------------------------- S_AO_Agent A []; S_AO_Agent B []; S_AO_Agent Predator []; S_AO_Agent Prey []; S_C M []; class="type">int Key; class="type">int phase; class="type">void ArrayShuffle(class="type">class="kw">double &arr []); }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_ACSm1::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; class=class="str">"cmt">//---------------------------------------------------------------------------- ArrayResize(A, popSize); ArrayResize(B, popSize); ArrayResize(Predator, popSize); ArrayResize(Prey, popSize); ArrayResize(M, popSize);
初始化与移动阶段的状态机写法
这段群体优化算法的落地代码把种群初始化和迭代移动拆成了明确的状态机。Init 阶段先对 A、B、Predator、Prey、M 五类数组各 popSize 个个体做坐标初始化,再用随机概率在 rangeMin 与 rangeMax 区间内撒点,phase 置 0 后返回 true,整个结构适合直接拷进 MT5 的 EA 模板里改参数。 Moving 函数靠 phase 变量驱动三步预热:phase 0 把 A 拷给临时数组 a 并自增;phase 1 把 a 的适应度回写 A,再把 B 拷给 a;phase 2 仅回写 B 的适应度。三步跑完才进正式的选择与捕食逻辑,这种分阶段写法能避免首代评估错位。 选择环节用 u.RNDprobab() < 0.5 决定 Predator 和 Prey 取自 A 还是 B,Key 标记来源;Prey 还会过一遍 ArrayShuffle 做乱序。外汇与贵金属行情的高随机性下,这类概率路由可能让同参数回测结果出现波动,建议跑满 30 次以上取中位数再下结论。
for (class="type">int i = class="num">0; i < popSize; i++) { A [i].Init(coords); B [i].Init(coords); Predator [i].Init(coords); Prey [i].Init(coords); M [i].Init(coords); } class=class="str">"cmt">// Initialization for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int j = class="num">0; j < coords; j++) { A [i].c [j] = rangeMin [j] + (rangeMax [j] - rangeMin [j]) * u.RNDprobab(); B [i].c [j] = rangeMin [j] + (rangeMax [j] - rangeMin [j]) * u.RNDprobab(); } } phase = class="num">0; class="kw">return true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_ACSm1::Moving() { class=class="str">"cmt">//---------------------------------------------------------------------------- if (phase == class="num">0) { for (class="type">int i = class="num">0; i < popSize; i++) ArrayCopy(a [i].c, A [i].c); phase++; class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- if (phase == class="num">1) { for (class="type">int i = class="num">0; i < popSize; i++) A [i].f = a [i].f; for (class="type">int i = class="num">0; i < popSize; i++) ArrayCopy(a [i].c, B [i].c); phase++; class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- if (phase == class="num">2) { for (class="type">int i = class="num">0; i < popSize; i++) B [i].f = a [i].f; phase++; } class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">// Selection if (u.RNDprobab() < class="num">0.5) { for (class="type">int i = class="num">0; i < popSize; i++) { Predator [i] = A [i]; } Key = class="num">1; } else { for (class="type">int i = class="num">0; i < popSize; i++) { Predator [i] = B [i]; } Key = class="num">2; } if (u.RNDprobab() < class="num">0.5) { for (class="type">int i = class="num">0; i < popSize; i++) { Prey [i] = A [i]; } } else { for (class="type">int i = class="num">0; i < popSize; i++) { Prey [i] = B [i]; } } class=class="str">"cmt">// Permutation of Prey for (class="type">int i = class="num">0; i < popSize; i++) { ArrayShuffle(Prey [i].c); } class="type">class="kw">double R; if (u.RNDprobab() < class="num">0.5) {