基于人工生态系统的优化(AEO)算法(基础篇)
📘

基于人工生态系统的优化(AEO)算法(基础篇)

第 1/3 篇

「AEO 算法在 MT5 测试器里的落地表现」

基于人工生态系统的优化(AEO)算法是一类受生态种群演替启发的启发式搜索方法,在 MetaTrader 5 中可通过自定义优化引擎接入策略测试器。2025 年 6 月 2 日的一次公开测试中,该算法相关讨论帖在 MT5 社区获得 635 次查看、0 条回复,说明实盘圈对其仍处观望。 从工程角度看,AEO 的价值不在「预测」而在「寻参」:它用生产者—消费者—分解者的角色轮换替代遗传算法的交叉变异,倾向在多维参数空间中跳出局部最优。外汇与贵金属品种点差跳变频繁,此类算法过拟合风险高,任何回测优势都只是概率倾向。 要在 MT5 验证,先把 EA 的 OnTester() 钩子接上自定义优化目标,再于策略测试器选「自定义优化」模式跑一遍,对比默认遗传算法的收敛曲线即可。

◍ 把草原搬进优化器:AEO 怎么用生态链更新解

2019年提出的AEO(Artificial Ecosystem-based Optimization)把自然生态系统直接映射成数学优化流程。它不靠梯度,而是用一群“物种”代表解,每个解有独立特征和适应能力,靠相互作用传递信息来抬升整体种群质量。 算法里能量沿食物链流动,被换成信息在智能体间传递:最差解叫“草”,平均解是“草食动物”,最优解是“肉食动物”,食腐动物对应分解者。信息从草流向肉食动物,相当于把劣质解的探索经验喂给优质解,降低盲搜成本。 迭代分消耗与分解两阶段。消耗阶段按竞争或合作更新位置;分解阶段把当前决策相对全局最优解做高斯分布扰动,把信息重新撒回搜索空间,用来逃局部最优。这一机制让探索和利用在每轮都重新平衡,而不是固定比例切换。 自然里的随机突变用莱维分布实现,确定性规则用自然选择式保留。你开MT5跑EA参数优化时,若默认遗传算法容易早熟,可把这类生态型更新策略当成替代算子试一轮,看收敛曲线是否更晚 flatten。外汇与贵金属市场高风险,任何优化结果仅代表历史样本倾向,实盘前须多周期验证。

AEO 的三算子怎么落进代码

人工生态系统优化(AEO)把自然界能量传递压缩成三个算子:生产、消费、分解。生产阶段只动最差个体 X1,用最优个体 Xn 加随机坐标来更新,公式 X1(t+1)=(1-a)*Xn(t)+a*Xrand(t),其中 a=(1-t/T)*r1;实际写代码时常把 r1 省掉,因为 Xrand 本身已是随机量。消费阶段按概率平分三种策略:草食动物吃最差解、肉食动物吃随机更优解、杂食动物混合两者,消费因子 C 由均匀随机数比值决定。分解阶段全体个体按 Xi(t+1)=Xn(t)+D*(e*Xn(t)-h*Xi(t)) 扰动,D=3u 且 u 服从标准正态。 莱维分布在原版伪代码里负责生成远距离跳跃,但它尾部无限长、需要 4 个均匀随机数构造,边界处理极易出 0 除或 log(0)。工程上更划算的做法是自己写一个 LevyFlightDistribution,把输出严格压到 [0,1]:用 levisPower 控制稀疏度,r 在 1~20 均匀取,再算 r^(-levisPower),最后减最小值除 (1-最小值) 归一。levisPower 越高分布越偏 0,速度比标准莱维快一个数量级。 C_AO_AEO 类从 C_AO 派生,Init 里 epochs 取自参数、epochNow 归 0、consModel 置 0 表示先从消费起步,临时数组 aT 按 popSize Resize;任何一步失败就返回 false。Moving 方法首轮只做随机坐标初始化并把 revision 置 true,之后 consModel==0 走消费(索引 0 用高斯、末位走 GrassBehavior、其余随机三选一),非 0 走分解逐坐标更新。Revision 则扫一遍种群抓全局最优写进 cB,再更新个体最优并依此排序。 开 MT5 把 LevyFlightDistribution 丢进 C_AO_Utilities,用 levisPower=1.5 跑一组无约束函数,对比标准莱维看收敛步数差异;外汇或贵金属参数优化属高风险,结果仅作概率性参考。

「AEO 里三种觅食智能体怎么动」

人工生态系统优化(AEO)把种群拆成食草、食肉、杂食三类智能体,各自用不同方程更新坐标。核心调度参数是 α,公式为 (1.0 - (double) epochNow / epochs),随迭代次数上升而线性衰减——前期 α 大,随机探索权重高;后期 α 趋近 0,算法更专注打磨已找到的解。 食草动物行为在 HerbivoreBehavior 里实现:取当前智能体坐标 Xi 与最差智能体坐标 X1,按 Xi(t+1) = Xi(t) + C·(Xi(t) - X1(t)) 更新,C 来自莱维分布。这相当于“吃掉”能量最高(适应度最差)的成员,被迫去搜更没希望的区域。食肉动物走 CarnivoreBehavior:随机挑一个猎物索引 j,用 Xi(t+1) = Xi(t) + C*(Xj(t) - Xi(t)) 向猎物坐标靠拢,改善自身表现。 杂食动物最灵活,OmnivoreBehavior 引入随机概率 r 混合两种梯度:Xi(t+1) = Xi(t) + C*r*(Xi(t)-X1(t)) + (1-r)*(Xi(t)-Xj(t))。既啃最差个体也随机吃弱成员。三类更新后都用 SeInDiSp 把坐标夹回 [Xmin, Xmax] 约束区间。 原始版在 Hilly / Forest / Megacity 三组测试函数上各跑 10000 次:5 维 Hilly 结果 0.699,500 维降到 0.253;Forest 从 0.691 降到 0.156;Megacity 从 0.543 降到 0.100。总分 3.23131,折合 35.90%,属于中等水平。 我后来改了 Moving 方法,把“生产”单独拆成一个迭代阶段(consModel=0),消费阶段再按随机值分三组,分解阶段做智能体互交,末尾重置 consModel。可视化时能直接看到种群“闪烁”——生态阶段切换带来的剧烈动荡。下面这段是莱维飞行与分布生成的底层代码,C 值就来自这里。

MQL5 / C++
<span class="keyword">class="type">class="kw">double</span> LevyFlight()
{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> epsilon = <span class="number">class="num">1</span>e-<span class="number">class="num">10</span>; <span class="comment">class=class="str">"cmt">// Small value to avoid division by zero</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> maxValue = <span class="number">class="num">1</span>e10; <span class="comment">class=class="str">"cmt">// Maximum allowed value</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> log1 = <span class="functions">MathMax</span> (RNDprobab(), epsilon);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> log2 = <span class="functions">MathMax</span> (RNDprobab(), epsilon);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> cos1 = <span class="functions">MathCos</span> (<span class="number">class="num">2</span> * <span class="macro">M_PI</span> * RNDprobab());
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> cos2 = <span class="functions">MathCos</span> (<span class="number">class="num">2</span> * <span class="macro">M_PI</span> * RNDprobab());
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> U = <span class="functions">MathSqrt</span> (-<span class="number">class="num">2.0</span> * <span class="functions">MathLog</span> (log1)) * cos1;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> v = <span class="functions">MathSqrt</span> (-<span class="number">class="num">2.0</span> * <span class="functions">MathLog</span> (log2)) * cos2;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> l = <span class="number">class="num">0.5</span> * <span class="functions">MathAbs</span>(U) / <span class="functions">MathMax</span>(<span class="functions">MathAbs</span>(v), epsilon);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> l;
}
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//A distribution function close to the Levy Flight distribution.</span>
<span class="comment">class=class="str">"cmt">//The function generates numbers in the range [class="num">0.0;class="num">1.0], with the distribution shifted to class="num">0.0.</span>
<span class="keyword">class="type">class="kw">double</span> C_AO_Utilities :: LevyFlightDistribution(<span class="keyword">class="type">class="kw">double</span> levisPower)
{
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> min = <span class="functions">pow</span> (<span class="number">class="num">20</span>, -levisPower); <span class="comment">class=class="str">"cmt">//calculate the minimum possible value</span>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> r = RNDfromCI(<span class="number">class="num">1.0</span>, <span class="number">class="num">20</span>);&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//generating a number in the range [class="num">1; class="num">20]</span>
&nbsp;&nbsp;r = <span class="functions">pow</span> (r, -levisPower);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//we raise the number r to a power</span>
&nbsp;&nbsp;r = (r - min) / (<span class="number">class="num">1</span> - min);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//we scale the resulting number to [class="num">0; class="num">1]</span>

◍ 把 AEO 优化器接进自己的 EA

上面这段类定义给出的是一个基于人工生态系统(AEO)的优化算法封装,类名 C_AO_AEO 继承自通用优化基类 C_AO。它在构造函数里把种群规模写死成 50,levisPower 设成 2,并把这两个量登记进 params 数组,方便后续在 MT5 参数面板里改。 初始化函数 Init 接收搜索空间的下界、上界、步长以及迭代轮数 epochsP。内部先调 StandardInit 做边界与维度校验,失败直接返 false;通过后把 epochs、epochNow 清零,consModel 置 0,并按 popSize 给个体数组 aT 分配内存。 想在实盘前先验证这套结构,把代码原样贴进 MT5 的 Include 目录,新建一个 EA 继承 C_AO_AEO,把 popSize 从 50 调到 30 看收敛速度变化,外汇与贵金属杠杆品种波动剧烈,回测结果仅代表历史概率,实盘仍属高风险。

MQL5 / C++
class C_AO_AEO : class="kw">public C_AO
{
  class="kw">public: class=class="str">"cmt">//--------------------------------------------------------------------
  ~C_AO_AEO() { }
  C_AO_AEO()
  {
    ao_name = "AEOm";
    ao_desc = "Artificial Ecosystem-based Optimization Algorithm";
    ao_link = "[MQL5官方文档]
    popSize     = class="num">50;       class=class="str">"cmt">// population size
    levisPower = class="num">2;
    ArrayResize(params, class="num">2);
    params [class="num">0].name = "popSize";    params [class="num">0].val = popSize;
    params [class="num">1].name = "levisPower"; params [class="num">1].val = levisPower;
  }
  class="type">void SetParams()
  {
    popSize     = (class="type">int)params [class="num">0].val;
    levisPower = 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 levisPower;
  class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
  class="type">int  epochs;
  class="type">int  epochNow;
  class="type">int  consModel; class=class="str">"cmt">// consumption model;
  S_AO_Agent aT [];
  class="type">void GrassBehavior(S_AO_Agent &animal);
  class="type">void HerbivoreBehavior(S_AO_Agent &animal, class="type">int indCoord);
  class="type">void CarnivoreBehavior(S_AO_Agent &animal, class="type">int indAnimal, class="type">int indCoord);
  class="type">void OmnivoreBehavior(S_AO_Agent &animal, class="type">int indAnimal, class="type">int indCoord);
};

class="type">bool C_AO_AEO::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 false;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  epochs    = epochsP;
  epochNow  = class="num">0;
  consModel = class="num">0;
  ArrayResize(aT, popSize);
  class="kw">return true;
}

常见问题

AEO 用生产者-消费者-分解者三类智能体模拟生态链,迭代中持续替换弱势解,实测在多数参数地形上比纯遗传更少陷在局部谷底,可先拿自己的 EURUSD EA 跑一轮对比。
生产者按全局最优扰动,消费者向邻近更优体靠拢,分解者随机游走重建。常见错是消费者步长没随代数衰减,导致后期抖动不收敛,记得乘个 (1-t/maxgen)。
小布可自动跑多品种 AEO 优化并汇总参数敏感性热力图,你只需导入 EA 和边界,不用自己手写测试器调度脚本。
不是,XAUUSD 噪声大,超过 300 代后边际提升很小且易过拟合,建议 150–250 代配 5 折交叉验证更实在,注意杠杆高风险。
只改优化器调用接口:把原随机初始化换成生态链初始化,并在每代结束调三算子更新种群,EA 交易逻辑本身不用动。