人工藻类算法(Artificial Algae Algorithm,AAA)·进阶篇
(2/3)· 从单细胞分裂到种群优化,AAA 的三大过程如何用坐标与能量重写交易参数搜索
接上篇对 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 ();
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 量级,能直观看出位移轨迹是否卡死。贵金属与外汇杠杆交易高风险,参数回测结论仅具概率意义。
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 观察收敛曲线。
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 策略测试器用历史数据验证。
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">//——————————————————————————————————————————————————————————————————————————————