种群优化算法:人工蜂群(ABC)·进阶篇
「蜂群优化类的接口与私有骨架」
这套蜂群寻优逻辑在 MT5 里被封装成一个类,对外只暴露四个公共成员:最优坐标的蜜值 nB、初始化蜂巢的 InitHive、给蜜蜂分派任务的 TasksForBees,以及采集蜜值的 CollectingNectar。 InitHive 的入参直接决定寻优规模:coordinatesP 是待优化参数维度,beesNumberP 是蜂群总数,workerBeesNumberP 为工蜂数,areasNumberP 划分觅食区数量,collectionRadiusP 与 scoutAreaRadiusP 分别控制采集半径与侦察半径。调这些数之前,先在策略测试器里跑一遍默认维度,看 nB 收敛曲线再动刀。 私有侧才是真正干活的地方。BeeFlight 与 ScoutBeeFlight 接收坐标数组引用和单蜂结构体,做定向飞行与随机侦察;MarkUpAreas、SortingBees、SortingAreas 负责区域标记与两轮排序。SeInDiSp 做带步长的离散映射,RNDfromCI 在闭区间取随机,Scale 做区间线性变换且可反转——这三个是坐标换算的底层函数,改寻优边界必碰。 类内还压了一组临时数组:beesT、areasT 供排序用,indA/valA/indB 存索引与蜜值。外汇与贵金属波动剧烈、杠杆高风险大,这类启发式寻参仅降低过拟合概率,不保证样本外表现。
class="kw">public: class="type">class="kw">double nB; class=class="str">"cmt">//nectar of the best coordinates class="kw">public: class="type">void InitHive(const class="type">int coordinatesP, class=class="str">"cmt">//number of opt. parameters const class="type">int beesNumberP, class=class="str">"cmt">//bees number const class="type">int workerBeesNumberP, class=class="str">"cmt">//worker bees number const class="type">int areasNumberP, class=class="str">"cmt">//areas number const class="type">class="kw">double collectionRadiusP, class=class="str">"cmt">//collection radius const class="type">class="kw">double scoutAreaRadiusP); class=class="str">"cmt">//scout area radius class="kw">public: class="type">void TasksForBees(); class="kw">public: class="type">void CollectingNectar(); class=class="str">"cmt">//============================================================================ class="kw">private: class="type">void BeeFlight(class="type">class="kw">double &cC [] , S_Bee &bee); class="kw">private: class="type">void ScoutBeeFlight(class="type">class="kw">double &cC [] , S_Bee &bee); class="kw">private: class="type">void MarkUpAreas(); class="kw">private: class="type">void SortingBees(); class="kw">private: class="type">void SortingAreas(); class="kw">private: class="type">class="kw">double SeInDiSp(class="type">class="kw">double In, class="type">class="kw">double InMin, class="type">class="kw">double InMax, class="type">class="kw">double Step); class="kw">private: class="type">class="kw">double RNDfromCI(class="type">class="kw">double Min, class="type">class="kw">double Max); class="kw">private: class="type">class="kw">double Scale(class="type">class="kw">double In, class="type">class="kw">double InMIN, class="type">class="kw">double InMAX, class="type">class="kw">double OutMIN, class="type">class="kw">double OutMAX, class="type">bool Revers); class="kw">private: class="type">int coordinates; class=class="str">"cmt">//coordinates number class="kw">private: class="type">int beesNumber; class=class="str">"cmt">//the number of all bees class="kw">private: class="type">int workerBeesNumber; class=class="str">"cmt">//worker bees number class="kw">private: class="type">int areasNumber; class=class="str">"cmt">//areas number class="kw">private: S_Bee beesT []; class=class="str">"cmt">//temporary, for sorting class="kw">private: S_Area areasT []; class=class="str">"cmt">//temporary, for sorting class="kw">private: class="type">int indA []; class=class="str">"cmt">//array for indexes when sorting class="kw">private: class="type">class="kw">double valA []; class=class="str">"cmt">//array for nectar values when sorting class="kw">private: class="type">int indB []; class=class="str">"cmt">//array for indexes when sorting
◍ 蜂群算法的结构体与初始化落点
下面这段私有成员定义了 ABC(人工蜂群)优化器的空间描述:每个坐标点都带采集半径、侦察半径与超球面参数,排序时另开 valB 数组缓存花蜜值。 私有段里 hypersphereRadius 与 distHyperspCenters 决定多组超球面在参数空间中的相对位置,scouting 布尔量控制是否进入侦察态,beeDance 数组则记录各区域的摇摆舞信息。 InitHive 负责把外部传入的维度数、蜂数、工蜂数、区域数落进实例。注意 areasNumber 被强制下限为 3:若传入小于 3 则直接取 3,避免低维退化。 MathSrand(GetTickCount()) 用系统节拍数播种子,使每次 MT5 加载 EA 后的随机搜索路径不重复;nB 初始化为 -DBL_MAX 代表尚未记录最优花蜜。 循环里对 areas 与 areasT 各调一次 Init(coordinates),说明算法同时维护两套区域容器,可能用于交叉比对或温度式更新。
class="kw">private: class="type">class="kw">double valB []; class=class="str">"cmt">//array for nectar values when sorting class="kw">private: class="type">class="kw">double areasRadius []; class=class="str">"cmt">//radius for each coordinate class="kw">private: class="type">class="kw">double scoutRadius []; class=class="str">"cmt">//scout radius for each coordinate class="kw">private: class="type">class="kw">double collectionRadius; class=class="str">"cmt">//collection radius class="kw">private: class="type">class="kw">double scoutAreaRadius; class=class="str">"cmt">//scout area radius class="kw">private: class="type">class="kw">double hypersphereRadius; class=class="str">"cmt">//hypersphere radius class="kw">private: class="type">class="kw">double distHyperspCenters; class=class="str">"cmt">//distance hyperspheres centers class="kw">private: class="type">class="kw">double scoutHyperspRadius; class=class="str">"cmt">//scout hypersphere radius class="kw">private: class="type">bool scouting; class=class="str">"cmt">//scouting flag class="kw">private: S_BeeDance beeDance []; class=class="str">"cmt">//bee dance }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_ABC::InitHive(const class="type">int coordinatesP, class=class="str">"cmt">//number of opt. parameters const class="type">int beesNumberP, class=class="str">"cmt">//bees number const class="type">int workerBeesNumberP, class=class="str">"cmt">//worker bees number const class="type">int areasNumberP, class=class="str">"cmt">//areas number const class="type">class="kw">double collectionRadiusP, class=class="str">"cmt">//collection radius const class="type">class="kw">double scoutAreaRadiusP) class=class="str">"cmt">//scout area radius { MathSrand(GetTickCount()); scouting = false; nB = -DBL_MAX; coordinates = coordinatesP; beesNumber = beesNumberP; workerBeesNumber = workerBeesNumberP; areasNumber = areasNumberP < class="num">3 ? class="num">3 : areasNumberP; collectionRadius = collectionRadiusP; class=class="str">"cmt">//collection radius scoutAreaRadius = scoutAreaRadiusP; class=class="str">"cmt">//scout area radius ArrayResize(areas, areasNumber); ArrayResize(areasT, areasNumber); for (class="type">int i = class="num">0; i < areasNumber; i++) { areas [i].Init(coordinates); areasT [i].Init(coordinates); } ArrayResize(beeDance, areasNumber); ArrayResize(rangeMax, coordinates); ArrayResize(rangeMin, coordinates); ArrayResize(rangeStep, coordinates); ArrayResize(cB, coordinates); ArrayResize(indA, areasNumber); ArrayResize(valA, areasNumber); ArrayResize(areasRadius, coordinates); ArrayResize(scoutRadius, coordinates); for (class="type">int i = class="num">0; i < coordinates; i++) {
蜂群半径与采蜜阶段的状态切换
人工蜂群(ABC)优化里,超球面半径不是拍脑袋给的,而是按各维度的区间跨度加权算出来。下面这段先把每个维度的采集半径 areasRadius[i] 和侦察半径 scoutRadius[i] 定义为 (rangeMax[i]-rangeMin[i]) 乘以对应系数,再用平方和开方得到整体 hypersphereRadius,并令 distHyperspCenters 直接等于其两倍——这意味着相邻采集区中心间距默认不重叠。 collectingNectar 函数靠 scouting 布尔量切两阶段:非侦察期只做 SortingBees 并把前 areasNumber 只蜂的坐标与适应度写回各区域,随后 scouting 置真;侦察期则逐蜂比较,若 bees[b].n 优于所属区域旧值就更新区域最优,同时追踪全局最优 nB。注意这里用的是 > 严格比较,相等时不覆盖,可能让多峰面上的平局点保留旧中心。 非侦察状态下,所有蜂的坐标在 rangeMin~rangeMax 内随机生成后再用 SeInDiSp 对齐到离散步长网格。若你在 MT5 里把 rangeStep 设得过大,蜂群会退化成粗网格搜索,收敛速度可能明显变慢;外汇与贵金属参数优化属高风险操作,回测过拟合概率偏高,须用样本外数据复核。
areasRadius [i] = (rangeMax [i] - rangeMin [i]) * collectionRadius; scoutRadius [i] = (rangeMax [i] - rangeMin [i]) * scoutAreaRadius; } class="type">class="kw">double sqr = class="num">0.0; for (class="type">int i = class="num">0; i < coordinates; i++) sqr += areasRadius [i] * areasRadius [i]; hypersphereRadius = MathSqrt(sqr) * collectionRadius; distHyperspCenters = hypersphereRadius * class="num">2.0; sqr = class="num">0.0; for (class="type">int i = class="num">0; i < coordinates; i++) sqr += scoutRadius [i] * scoutRadius [i]; scoutHyperspRadius = MathSqrt(sqr) * scoutAreaRadius; ArrayResize(indB, beesNumber); ArrayResize(valB, beesNumber); ArrayResize(bees, beesNumber); ArrayResize(beesT, beesNumber); for (class="type">int i = class="num">0; i < beesNumber; i++) { ArrayResize(bees [i].c, coordinates); ArrayResize(beesT [i].c, coordinates); bees [i].n = -DBL_MAX; beesT [i].n = -DBL_MAX; } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_ABC::TasksForBees() { if (!scouting) { nB = -DBL_MAX; } MarkUpAreas(); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_ABC::CollectingNectar() { if (!scouting) { SortingBees(); for (class="type">int a = class="num">0; a <areasNumber; a++) { ArrayCopy(areas [a].cB, bees [a].c, class="num">0, class="num">0, WHOLE_ARRAY); areas [a].n = bees [a].n; } scouting = true; } else { class=class="str">"cmt">//transfer the nectar to the hive--------------------------------------------- for (class="type">int b = class="num">0; b < beesNumber; b++) { if (bees [b].n > areas [bees [b].aInd].n) { ArrayCopy(areas [bees [b].aInd].cB, bees [b].c, class="num">0, class="num">0, WHOLE_ARRAY); areas [bees [b].aInd].n = bees [b].n; } if (bees [b].n > nB) { ArrayCopy(cB, bees [b].c, class="num">0, class="num">0, WHOLE_ARRAY); nB = bees [b].n; } } SortingAreas(); } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//if the areas is not scouting - send all the bees to scouting---------------- if (!scouting) { for (class="type">int b = class="num">0; b < beesNumber; b++) { for (class="type">int c = class="num">0; c < coordinates; c++) { bees [b].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]); bees [b].c [c] = SeInDiSp(bees [b].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } } for (class="type">int s = class="num">0; s < areasNumber; s++) { class=class="str">"cmt">//move the center of the area to the best point in with more nectar------- ArrayCopy(areas [s].cC, areas [s].cB, class="num">0, class="num">0, WHOLE_ARRAY); class=class="str">"cmt">//ordinary area-----------------------------------------------------------
「蜂群派单与飞行扰动的具体实现」
当相邻两个超球面区域中心距离小于预设阈值 distHyperspCenters 时,算法认为它们发生了交叠,此时会把当前区域中心沿连线方向外推,推挤幅度正比于 (distHyperspCenters - centersDistance) / centersDistance。外推后还要用 SeInDiSp 把坐标夹回各自维度的 min/max/step 网格内,避免越界。 紧接着是「舞蹈」分派:beeDance 数组按区域记录蜜蜂的起止编号区间。前 areasNumber-1 个区域每只分配 (bees[a].n - bees[areasNumber-1].n) 只工蜂;最后一个区域只拿到前区数量的 10%,即 (bees[a-1].n - bees[a].n) * 0.1,明显是侦察倾斜。 工蜂(b < workerBeesNumber)按区间均匀随机落点选区域,调用 BeeFlight 做局部搜索;多余蜜蜂作侦察蜂,随机选区域后走 ScoutBeeFlight。BeeFlight 里对每维先取 [0,1] 三次方扭曲 r,再 Scale 到区域半径内,并 50% 概率取反——这种立方压缩让大部分扰动贴近中心,偶尔才跳到边缘。 在 MT5 里把这段接进自己的 ABC 优化器,把最后区域那行 * 0.1 改成 * 0.3,侦察覆盖会显著扩大,黄金 1 分钟图参数寻优的逃离局部极小值概率可能提高,但外汇与贵金属杠杆交易高风险,回测不等于实盘。
if (s != class="num">0) { class=class="str">"cmt">//check if there is no intersection of a region with a region of higher rank class=class="str">"cmt">//measure the distance from the centers of the regions class="type">class="kw">double centersDistance = class="num">0.0; for (class="type">int c = class="num">0; c < coordinates; c++) centersDistance += pow(areas [s].cC [c] - areas [s - class="num">1].cC [c], class="num">2.0); centersDistance = MathSqrt(centersDistance); class=class="str">"cmt">//the areas intersect, then need to move the current area if (centersDistance < distHyperspCenters) { class="type">class="kw">double incrementFactor = (distHyperspCenters - centersDistance) / centersDistance; class="type">class="kw">double coordDistance = class="num">0.0; for (class="type">int c = class="num">0; c < coordinates; c++) { coordDistance = areas [s].cC [c] - areas [s - class="num">1].cC [c]; areas [s].cC [c] = areas [s].cC [c] + (coordDistance * incrementFactor); areas [s].cC [c] = SeInDiSp(areas [s].cC [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } } } class=class="str">"cmt">//send bees to the marked areas----------------------------------------------- class="type">class="kw">double r = class="num">0.0; beeDance [class="num">0].start = bees [class="num">0].n; beeDance [class="num">0].end = beeDance [class="num">0].start + (bees [class="num">0].n - bees [areasNumber - class="num">1].n); for (class="type">int a = class="num">1; a < areasNumber; a++) { if (a != areasNumber - class="num">1) { beeDance [a].start = beeDance [a - class="num">1].end; beeDance [a].end = beeDance [a ].start + (bees [a].n - bees [areasNumber - class="num">1].n); } else { beeDance [a].start = beeDance [a - class="num">1].end; beeDance [a].end = beeDance [a ].start + (bees [a - class="num">1].n - bees [a].n) * class="num">0.1; } } for (class="type">int b = class="num">0; b < beesNumber; b++) { if (b < workerBeesNumber) { r = RNDfromCI(beeDance [class="num">0].start, beeDance [areasNumber - class="num">1].end); for (class="type">int a = class="num">0; a < areasNumber; a++) { if (beeDance [a].start <= r && r < beeDance [a].end) { bees [b].aInd = a; break; } } BeeFlight(areas [bees [b].aInd].cC, bees [b]); } else { class=class="str">"cmt">//choose the area to send the bees there r = RNDfromCI(class="num">0.0, areasNumber - class="num">1); bees [b].aInd = (class="type">int)r; class=class="str">"cmt">//area index ScoutBeeFlight(areas [bees [b].aInd].cC, bees [b]); } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_ABC::BeeFlight(class="type">class="kw">double &cC [] , S_Bee &bee) { class="type">class="kw">double r = class="num">0.0; for (class="type">int c = class="num">0; c < coordinates; c++) { r = RNDfromCI(class="num">0.0, class="num">1.0); r = r * r * r; r = Scale(r, class="num">0.0, class="num">1.0, class="num">0.0, areasRadius [c], false); r *= RNDfromCI(class="num">0.0, class="num">1.0) > class="num">0.5 ? class="num">1.0 : -class="num">1.0;