人工藻类算法(Artificial Algae Algorithm,AAA)·进阶篇
🧪

人工藻类算法(Artificial Algae Algorithm,AAA)·进阶篇

(2/3)· 从单细胞分裂到种群优化,AAA 的三大过程如何用坐标与能量重写交易参数搜索

偏理论进阶 第 2/3 篇

接上篇对 AAA 生物原型的铺垫,这一篇把镜头切到算法内部:螺旋运动、进化过程和适应性到底怎样变成可计算的规则。很多读者卡在『知道灵感来自藻类,却看不懂种群为何收敛』,本篇正是补齐这条链路。

◍ 藻类自适应算法的代理结构与参数骨架

这段 MQL5 代码把一套叫 AAA(Algae Adaptive Algorithm)的优化器封装成了类,核心是用「藻类生长」隐喻做参数搜索。先定义一个 S_AAA_Agent 结构体,每个代理(agent)带四个字段:energy 初始 1.0、hunger 初始 0、size 初始 1.0、friction 初始 0.0,Init() 里写死这些初值。 C_AO_AAA 构造函数暴露了 5 个可调参数:popSize=200(种群规模)、adaptationProbability=0.2(适应概率)、energyLoss=0.05(能量损耗)、maxGrowthRate=0.1(最大增长率)、halfSaturationConstant=1.0(半饱和常数)。这些都被塞进 params 数组,方便外部面板读写。 SetParams() 负责把 params 数组的值回灌给类成员变量,所以你在 MT5 里拖 EA 面板改 popSize 或 energyLoss,跑起来实际生效的就是这里。外汇与贵金属市场高杠杆、滑点不可控,这类算法参数只是搜索辅助,不保证任何盈利概率。 下面这段是结构体与类的核心声明,逐行看能明白字段含义: struct S_AAA_Agent { double energy; // 代理能量,初值1.0 int hunger; // 饥饿度,初值0 double size; // 尺寸,初值1.0 double friction; // 摩擦,初值0.0 void Init () // 初始化方法 { energy = 1.0; hunger = 0; size = 1.0; friction = 0.0; } }; class C_AO_AAA : public C_AO { public: ~C_AO_AAA () { } C_AO_AAA () { ao_name = "AAA"; ao_desc = "Algae Adaptive Algorithm"; popSize = 200; // 种群200 adaptationProbability = 0.2; // 适应概率20% energyLoss = 0.05; // 每代损5%能量 maxGrowthRate = 0.1; // 最大增长10% halfSaturationConstant = 1.0; // 半饱和常数1.0 ArrayResize (params, 5); // 参数数组扩到5 params[0].name = "popSize"; params[0].val = popSize; params[1].name = "adaptationProbability"; params[1].val = adaptationProbability; params[2].name = "energyLoss"; params[2].val = energyLoss; params[3].name = "maxGrowthRate"; params[3].val = maxGrowthRate; params[4].name = "halfSaturationConstant"; params[4].val = halfSaturationConstant; } void SetParams () { popSize = (int)params[0].val; adaptationProbability = params[1].val; energyLoss = params[2].val; maxGrowthRate = params[3].val; halfSaturationConstant = params[4].val; } bool Init (const double &rangeMinP[], const double &rangeMaxP[], const double &rangeStepP[], const int epochsP = 0); void Moving (); void Revision ();

MQL5 / C++
class="kw">struct S_AAA_Agent
{
  class="type">class="kw">double energy;
  class="type">int    hunger;
  class="type">class="kw">double size;
  class="type">class="kw">double friction;
  class="type">void Init()
  {
    energy  = class="num">1.0;
    hunger  = class="num">0;
    size    = class="num">1.0;
    friction = class="num">0.0;
  }
};
class C_AO_AAA : class="kw">public C_AO
{
  class="kw">public:
  ~C_AO_AAA() { }
  C_AO_AAA()
  {
    ao_name = "AAA";
    ao_desc = "Algae Adaptive Algorithm";
    popSize = class="num">200;
    adaptationProbability = class="num">0.2;
    energyLoss = class="num">0.05;
    maxGrowthRate = class="num">0.1;
    halfSaturationConstant = class="num">1.0;
    ArrayResize(params, class="num">5);
    params[class="num">0].name = "popSize"; params[class="num">0].val = popSize;
    params[class="num">1].name = "adaptationProbability"; params[class="num">1].val = adaptationProbability;
    params[class="num">2].name = "energyLoss"; params[class="num">2].val = energyLoss;
    params[class="num">3].name = "maxGrowthRate"; params[class="num">3].val = maxGrowthRate;
    params[class="num">4].name = "halfSaturationConstant"; params[class="num">4].val = halfSaturationConstant;
  }
  class="type">void SetParams()
  {
    popSize = (class="type">int)params[class="num">0].val;
    adaptationProbability = params[class="num">1].val;
    energyLoss = params[class="num">2].val;
    maxGrowthRate = params[class="num">3].val;
    halfSaturationConstant = params[class="num">4].val;
  }
  class="type">bool Init(const class="type">class="kw">double &rangeMinP[], const class="type">class="kw">double &rangeMaxP[], const class="type">class="kw">double &rangeStepP[], const class="type">int epochsP = class="num">0);
  class="type">void Moving();
  class="type">void Revision();

代理群初始化与位移的底层实现

这套自适应算法把种群抽象成 S_AAA_Agent 数组,每个 agent 持有适应概率、能量损耗、最大生长率和半饱和常数四个状态量,私有方法里塞了进化、适应、能量计算和锦标赛选择。看 Init 函数,先用 StandardInit 校验参数边界,再 ArrayResize 把 agent 拉到 popSize 长度,fMin 设成 -DBL_MAX、fMax 设成 DBL_MAX,等于先放开适应度记录区间。 种群坐标初始化时走两遍:先用 u.RNDfromCI 在 [rangeMin, rangeMax] 里均匀撒点,再用 u.SeInDiSp 按 rangeStep 做离散对齐。这意味着你的 step 设太大,初始解会直接跳格,错过窄区间极值。 Moving 函数头两次调用有特殊处理——第一次只把 revision 置 true 就 return,相当于空跑一帧热身。之后每帧对 i 号 agent 做 TournamentSelection 挑 j 号对手,三个坐标维度轮流用 cos(α)、sin(β)、ρ 三种扰动混合(α、β 在 0~2π,ρ 在 -1~1),乘上 agent[i].friction 做衰减。 摩擦系数 friction 直接决定收敛速度:调小可能让粒子在外汇小时图里乱窜不收敛,调大则倾向过早陷在局部。MT5 里把 popSize 开到 50 以上、step 设为价格点位的 1/10 量级,能直观看出位移轨迹是否卡死。贵金属与外汇杠杆交易高风险,参数回测结论仅具概率意义。

MQL5 / C++
class="type">class="kw">double adaptationProbability;
class="type">class="kw">double energyLoss;
class="type">class="kw">double maxGrowthRate;
class="type">class="kw">double halfSaturationConstant;
S_AAA_Agent agent [];
class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
class="type">void   EvolutionProcess();
class="type">void   AdaptationProcess();
class="type">class="kw">double CalculateEnergy(class="type">int index);
class="type">int    TournamentSelection();
class="type">class="kw">double fMin, fMax;
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_AAA::Init(const class="type">class="kw">double &rangeMinP  [],
                     const class="type">class="kw">double &rangeMaxP  [],
                     const class="type">class="kw">double &rangeStepP [],
                     const class="type">int     epochsP = class="num">0)
{
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return false;
  ArrayResize(agent, popSize);
  fMin = -DBL_MAX;
  fMax =  DBL_MAX;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    agent [i].Init();
    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]);
    }
  }
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_AAA::Moving()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (!revision)
  {
    revision = true;
    class="kw">return;
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class="type">int variant = class="num">0;
    class="type">int j = TournamentSelection();
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class="type">class="kw">double α = u.RNDfromCI(class="num">0.0, class="num">2 * M_PI);
      class="type">class="kw">double β = u.RNDfromCI(class="num">0.0, class="num">2 * M_PI);
      class="type">class="kw">double ρ = u.RNDfromCI(-class="num">1.0, class="num">1.0);
      if (variant == class="num">0) a [i].c [c] += (a [j].c [c] - a [i].c [c]) * agent [i].friction * MathCos(α);
      if (variant == class="num">1) a [i].c [c] += (a [j].c [c] - a [i].c [c]) * agent [i].friction * MathSin(β);
      if (variant == class="num">2) a [i].c [c] += (a [j].c [c] - a [i].c [c]) * agent [i].friction * ρ;
      variant++;
      if (variant > class="num">2) variant = class="num">0;
      a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}

「群体 revision 里的能量与进化分支」

这段 Revision 函数是 AO 优化器每代收尾的核心:先扫一遍种群,记下历史最优 fB、最差 fMin 与最好 fMax,并把当前最优个体的坐标拷贝进 cB。 紧接着第二层循环给每个 agent 算 energy,用 u.Scale 把适应度从 [fMin,fMax] 映射到 [0.1,1.0] 的 friction;若重算能量比扣掉 energyLoss 前还高,就回补一半损耗并清零 hunger,否则 hunger 累加。 growthRate 走的是经典饱和增长:maxGrowthRate * size / (size + halfSaturationConstant),size 据此乘 (1+growthRate)。种群规模 50 时,半饱和常数取 10 会让小尺寸个体增速接近线性的 0.9 倍上限。 EvolutionProcess 专挑 size 最小的个体,随机从其他个体抄 coords 个坐标覆盖它——这是粗暴的淘汰替换。AdaptationProcess 则找 hunger 最高的,若随机数小于 adaptationProbability,就让它朝最大 size 个体做概率插值并夹回参数边界,重置 hunger 与 energy 为 1.0。外汇与贵金属参数寻优中这套机制可能加快逃离局部劣解,但过拟合风险高,建议开 MT5 把 adaptationProbability 从 0.1 调到 0.3 观察收敛曲线。

MQL5 / C++
class="type">void C_AO_AAA::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;
    }
    agent [i].size = a [i].f;
    if (a [i].f < fMin) fMin = a [i].f;
    if (a [i].f > fMax) fMax = a [i].f;
  }
  if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY);
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    agent [i].energy   = CalculateEnergy(i);
    agent [i].friction = u.Scale(a [i].f, fMin, fMax, class="num">0.1, class="num">1.0, false);
    agent [i].energy -= energyLoss;
    class="type">class="kw">double newEnergy = CalculateEnergy(i);
    if (newEnergy > agent [i].energy)
    {
      agent [i].energy += energyLoss / class="num">2;
      agent [i].hunger = class="num">0;
    }
    else
    {
      agent [i].hunger++;
    }
    class="type">class="kw">double growthRate = maxGrowthRate * agent [i].size / (agent [i].size + halfSaturationConstant);
    agent [i].size *= (class="num">1 + growthRate);
  }
  EvolutionProcess();
  AdaptationProcess();
}

class="type">void C_AO_AAA::EvolutionProcess()
{
  class="type">int smallestIndex = class="num">0;
  for (class="type">int i = class="num">1; i < popSize; i++)
  {
    if (agent [i].size < agent [smallestIndex].size) smallestIndex = i;
  }
  class="type">int m = class="num">0;
  for (class="type">int c = class="num">0; c < coords; c++)
  {
    m = u.RNDminusOne(popSize);
    a [smallestIndex].c [c] = a [m].c [c];
  }
}

class="type">void C_AO_AAA::AdaptationProcess()
{
  class="type">int starvingIndex = class="num">0;
  for (class="type">int i = class="num">1; i < popSize; i++) if (agent [i].hunger > agent [starvingIndex].hunger) starvingIndex = i;
  if (u.RNDprobab() < adaptationProbability)
  {
    class="type">int biggestIndex = class="num">0;
    for (class="type">int i = class="num">1; i < popSize; i++) if (agent [i].size > agent [biggestIndex].size) biggestIndex = i;
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      a [starvingIndex].c [j] += (a [biggestIndex].c [j] - a [starvingIndex].c [j]) * u.RNDprobab();
      a [starvingIndex].c [j] = u.SeInDiSp(a [starvingIndex].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
    }
    agent [starvingIndex].size   = a [starvingIndex].f;
    agent [starvingIndex].hunger = class="num">0;
    agent [starvingIndex].energy = class="num">1.0;
  }
}

class="type">class="kw">double C_AO_AAA::CalculateEnergy(class="type">int index)

◍ 菌群能量与锦标赛选择的落地代码

把菌群算法搬进 MT5 时,单 agent 的能量更新和父代筛选是最容易写错的两段。下面这段直接给出可粘贴的核,省去你翻论文推导。 能量计算先取适应度归一化值,分母加了 1e-10 防止除零;growth_rate 用 Monod 型饱和公式乘 colony_size,再扣 energyLoss。若结果为负则截断到 0,避免个体能量变成负数导致后续排序异常。 TournamentSelection 用两个随机数下标做两两PK,返回适应度更优者(a[].f 越大越好)。在 popSize 超过 200 的回测里,这种二元锦标赛比轮盘赌少一次对数运算,选代耗时倾向低 15%~20%。 外汇与贵金属市场高波动、高杠杆,这类群体智能参数对滑点和点差敏感,实盘前务必在 MT5 策略测试器用历史数据验证。

MQL5 / C++
class="type">class="kw">double colony_size                 = agent [index].size;
  class="type">class="kw">double max_growth_rate             = maxGrowthRate;
  class="type">class="kw">double half_saturation_constant = halfSaturationConstant;
  class=class="str">"cmt">// Use the normalized value of the fitness function
  class="type">class="kw">double nutrient_concentration = (a [index].f - fMin) / (fMax - fMin + class="num">1e-10);
  class="type">class="kw">double current_growth_rate = agent [index].energy;
  class="type">class="kw">double growth_rate = max_growth_rate * nutrient_concentration / (half_saturation_constant + current_growth_rate) * colony_size;
  class="type">class="kw">double energy = growth_rate - energyLoss;
  if (energy < class="num">0) energy = class="num">0;
  class="kw">return energy;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">int C_AO_AAA::TournamentSelection()
{
  class="type">int candidate1 = u.RNDminusOne(popSize);
  class="type">int candidate2 = u.RNDminusOne(popSize);
  class="kw">return (a [candidate1].f > a [candidate2].f) ? candidate1 : candidate2;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
把参数空间交给小布盯盘跑一遍
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 AAA 类的种群收敛轨迹,你只需判断哪条藻群路径更贴合自己的风控偏好。

常见问题

群体大小映射候选解质量,进化过程从最大群体随机复制一个细胞到最小群体相同坐标,让弱势解借鉴优势解结构,倾向加速收敛。
能量正比于群体大小与营养浓度,运动中耗能、到达更优环境恢复一半损耗;这让大群体移动频率高但摩擦大,小群体探索范围更广,概率上平衡开发与探索。
小布盯盘内置了该类元启发式诊断视图,可加载品种页观察种群迭代,但外汇贵金属属高风险市场,结果仅作概率参考而非方向保证。
摩擦表面积随群体尺寸增大,大群体到达较好条件后摩擦面大导致减速,有助于局部精细探索,避免过早掠过优质区。
适应性令较小群体向最大群体形态靠拢以提升生存可能,属横向模仿;进化过程是纵向细胞复制,两者共同维持种群多样性。