循环孤雌生殖算法(CPA)·进阶篇
CPA 算法的种群初始化与首轮位置撒点
在 MT5 里实现殖民竞争算法(CPA)做参数寻优时,Init 函数先调用 StandardInit 校验区间与步长数组,再把总种群 popSize 按殖民地数 Nc 切成 Nm=popSize/Nc 个基础群。性别比例由 Fr 控制:fNumber=int(Nm*Fr),若算出来小于 1 则强制置 1,剩余 mNumber=Nm-fNumber 为雄性数量。 首轮 Moving 靠 revision 标志位区分:未修订时对每个个体 i 的 coords 个维度,先用 u.RNDfromCI 在 [rangeMin,rangeMax] 内取随机值,再用 u.SeInDiSp 对齐到离散步长网格,保证初始解落在可回测的参数点上。这一步直接决定后续寻优是否踩中 MT5 策略测试器的合法输入。 进入迭代后,搜索强度按 k=(epochs-epochNow)/epochs 线性衰减,epochs 为 0 时 k 会除零得到 NaN,所以 Init 传参务必给正整数轮次。雌性个体走孤雌更新:a[ind].c[c] = a[ind].cP[c] + alpha1*k*u.GaussDistribution(0.0,-1.0,1.0,8)*(rangeMax[c]-rangeMin[c]),其中高斯采样截断在 ±8σ,避免极端跳变。外汇与贵金属 EA 调参属高风险,回测优解实盘可能失效。 让小布替你跑这套 把 Init 里 epochsP 设成 50、Fr 设 0.3,在 EURUSD 的 M15 上跑一次,观察 fNumber 是否如预期为 Nm*0.3 并向下取整,能快速验证你抄的 CPA 类有没有把性别切分写反。
S_AO_Agent aT []; class=class="str">"cmt">// temporary colony for sorting class="type">void SortFromTo(S_AO_Agent &p [], S_AO_Agent &pTemp [], class="type">int from, class="type">int count); class=class="str">"cmt">// agent sorting function }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">// Initialization of the algorithm with the given search parameters class="type">bool C_AO_CPA::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; class=class="str">"cmt">//---------------------------------------------------------------------------- epochs = epochsP; epochNow = class="num">0; class=class="str">"cmt">// Calculating the colony size and the number of individuals of each gender Nm = popSize / Nc; fNumber = class="type">int(Nm * Fr); if (fNumber < class="num">1) fNumber = class="num">1; mNumber = Nm - fNumber; ArrayResize(aT, Nm); class="kw">return true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">// The main function for moving individuals in the search space class="type">void C_AO_CPA::Moving() { epochNow++; class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">// Initial random initialization of positions if this is the first iteration if (!revision) { for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { class=class="str">"cmt">// Generate a random position in a given range 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]); } } revision = true; class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">// Calculate the search power decay rate over time class="type">class="kw">double k = (epochs - epochNow)/(class="type">class="kw">double)epochs; class="type">int ind = class="num">0; class="type">int indF = class="num">0; class=class="str">"cmt">// Handling each colony for (class="type">int col = class="num">0; col < Nc; col++) { class=class="str">"cmt">// Updating the positions of female individuals(parthenogenesis) for (class="type">int f = class="num">0; f < fNumber; f++) { ind = col * Nm + f; for (class="type">int c = class="num">0; c < coords; c++) { class=class="str">"cmt">// Parthenogenetic position update using normal distribution a [ind].c [c] = a [ind].cP [c] + alpha1 * k * u.GaussDistribution(class="num">0.0, -class="num">1.0, class="num">1.0, class="num">8) * (rangeMax [c] - rangeMin [c]); } }
◍ 殖民竞争算法里的位置修订与跨群迁移
在殖民竞争优化(CPA)的每轮迭代末尾,Revision() 负责把当前种群里的最优解挑出来并更新全局最优。代码用一次线性扫描比对 a[i].f 与 fB,一旦发现更优个体就记下索引并拷贝坐标数组到 cB,这一步直接决定了下一轮搜索的引力中心。 个体当前位置会被整体存进 cP 备用,随后每个殖民按目标函数值排序。SortFromTo() 接收起始偏移 from 与数量 count,把待排区段抽进临时数组再回写,保证每群里优等个体永远排在头部。 跨群飞行(迁移)受概率 Pf 控制:当 u.RNDprobab() 小于 Pf 时才触发。随机抽两个不同殖民,若前者头部不如后者则交换编号,再把优群的最优坐标覆盖到劣群的最差位置,并重排劣群。这种机制让落后群体可能以较低代价跳出现有局部洼地。 下面这段是 Revision() 与排序辅助的核心节选,注意 ArrayCopy 的 WHOLE_ARRAY 用法以及 do-while 保证两群互异:
class=class="str">"cmt">// Function for revising positions and exchanging information between colonies class="type">void C_AO_CPA::Revision() { class=class="str">"cmt">// Find and update the best solution 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 (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">// Save the current positions for (class="type">int i = class="num">0; i < popSize; i++) { ArrayCopy(a [i].cP, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } class=class="str">"cmt">// Sort individuals in each colony by the target function value for (class="type">int col = class="num">0; col < Nc; col++) { ind = col * Nm; SortFromTo(a, aT, ind, Nm); } class=class="str">"cmt">// Mechanism of flight(migration) between colonies if (u.RNDprobab() < Pf) { class="type">int indCol_1 = class="num">0; class="type">int indCol_2 = class="num">0; class=class="str">"cmt">// Select two random different colonies indCol_1 = u.RNDminusOne(Nc); do indCol_2 = u.RNDminusOne(Nc); while (indCol_1 == indCol_2); class=class="str">"cmt">// Ensure that the best solution is in the first colony if (a [indCol_1 * Nm].f < a [indCol_2 * Nm].f) { class="type">int temp = indCol_1; indCol_1 = indCol_2; indCol_2 = temp; } class=class="str">"cmt">// Copy the best solution to the worst colony ArrayCopy(a [indCol_2 * Nm + Nm - class="num">1].cP, a [indCol_1 * Nm].cP, class="num">0, class="num">0, WHOLE_ARRAY); class=class="str">"cmt">// Re-sort the colony after migration SortFromTo(a, aT, indCol_2 * Nm, Nm); } } class=class="str">"cmt">// Auxiliary function for sorting agents by the value of the objective function class="type">void C_AO_CPA::SortFromTo(S_AO_Agent &p [], S_AO_Agent &pTemp [], class="type">int from, class="type">int count) { class="type">int cnt = class="num">1; class="type">int t0 = class="num">0; class="type">class="kw">double t1 = class="num">0.0; class="type">int ind []; class="type">class="kw">double val []; ArrayResize(ind, count); ArrayResize(val, count); class=class="str">"cmt">// Copy values for sorting for (class="type">int i = class="num">0; i < count; i++)
「用冒泡法给价格片段排个序」
这段逻辑干的事很直接:先把索引和对应数值按偏移量 from 装进两个数组,随后跑一轮冒泡排序,目标是把 val 按从大到小排好。 外层 while 用 cnt 当交换计数器,只要这一轮发生过交换就再来一遍,直到 cnt 归零,说明序列已经稳定。内层 for 从 0 比到 count-2,只要 val[i] 小于后一个,就同时交换 ind 和 val 两个数组里的位置,保证索引和数值始终绑在一起。 排序完之后,用 ind 里的顺序把原结构 p 抽到 pTemp,再按 from 偏移写回 p。你在 MT5 里把 count 设成 50、from 设成 0,跑一遍就能看到最近 50 根里谁是最大值排在最前。外汇和贵金属波动快,这种重排只是预处理,信号真假还得结合上下文判断。
{
ind [i] = i + from;
val [i] = p [i + from].f;
}
class=class="str">"cmt">// Bubble sort in descending order
while (cnt > class="num">0)
{
cnt = class="num">0;
for (class="type">int i = class="num">0; i < count - class="num">1; i++)
{
if (val [i] < val [i + class="num">1])
{
class=class="str">"cmt">// Exchange of indices
t0 = ind [i + class="num">1];
ind [i + class="num">1] = ind [i];
ind [i] = t0;
class=class="str">"cmt">// Exchange values
t1 = val [i + class="num">1];
val [i + class="num">1] = val [i];
val [i] = t1;
cnt++;
}
}
}
class=class="str">"cmt">// Apply the sorting results
for (class="type">int i = class="num">0; i < count; i++) pTemp [i] = p [ind [i]];
for (class="type">int i = from; i < from + count; i++) p [i] = pTemp [i - from];
}CPA 实测跑分与排名落点
把 CPA(周期性单性生殖算法)接到标准测试函数上跑完一轮,结果谈不上亮眼。它在 5/25/500 三个维度档位的 Hilly、Forest、Megacity 函数上各跑 10000 次,综合总分 3.12805,折算下来只摸到理论满分的 34.76%,最终在 45 种群体优化算法里排第 44。 具体看单组数值:Hilly 上 5/25/500 维结果分别是 0.7166、0.4001、0.2550;Forest 对应为 0.6218、0.3365、0.1926;Megacity 离散场景则是 0.3431、0.1677、0.0946。维度一拉高,收敛质量掉得很快,500 维基本腰斩再腰斩。 有意思的是肉眼可见的搜索轨迹——虚拟蚜虫在高维空间里分成好几个小群,群内移动和群体漂移都能直接看图分辨,这说明它的种群分化机制确实在起作用,只是单群精度撑不起高分。 横向比一下头部:排第一的 ANS 跨邻域搜索总分 6.134(68.15%),第十的 ACS 人工协同搜索也有 5.226(58.06%)。CPA 和榜首差了一倍多,做 MT5 自定义优化器时拿它当主力大概率不够用,更合适的位置是混合架构里的多样性扰动子模块。
◍ 优化器横向跑分:COAm 仍领先随机游走
上面这张表把多种元启发式优化器在 MT5 参数寻优任务里的表现摊开了看。COAm(布谷鸟优化算法改进版)综合得分 3.349,耗时 37.21 单位,随机游走(RW)只有 2.348 和 26.09,差距接近 43% 的综合分,说明盲目扰动在多维参数面上效率偏低。 带 M 标记的算法(COAm、SDOm、NMm、FAm)都做了某种变异策略改进,前三者综合分都在 3.2 以上,而 BBBC、CPA 这类无 M 标记的常规版掉到 3.1 附近。萤火虫算法 FAm 虽然综合 3.048,但第三项指标 0.32276 是全表最高,意味着它在局部精细搜索上有特长。 做 EA 调参时,若你的参数维度超过 8 个,直接套 RW 当优化器基本是在浪费回测时间;从这张表看,COAm 和改进型 Nelder-Mead 是更稳的起步选择,但实盘前必须重跑一遍,外汇与贵金属杠杆品种滑点会改写这些数值。
「画得少,看得清」
CPA 这套蚜虫隐喻在 MT5 回测台跑出来的评分区间被标成 0–100,100 只是理论天花板;附带的 CalculationTestResults.mqh 能直接算出排名表,有兴趣自己拉 CPA.zip 跑 Test_AO_CPA.mq5 验证。 它真正留下的不是收敛精度,而是把「有性 / 无性繁殖」「群体迁移」翻译成参数时的别扭:Nm、Fr、Pf、alpha1、alpha2 任一个偏移,解的质量就塌方,说明结构本身扛不住高维搜索。 所以别盯着它能不能实盘,把它当反面教材更划算——群体划分若没有强效迁移,大概率早收敛;生物行为巧妙不等于数学可优化。打开 github 里 Population-optimization-algorithms-MQL5 对照其他算法,你能少走很多弯路。