种群优化算法:Boids(虚拟生物)算法·进阶篇
Boids 寻优类的成员与初始化拆解
这段声明定义了一个基于鸟群行为(Boids)的优化代理类,把聚拢、分离、对齐三类作用力权重和距离阈值都暴露为可调双精度字段,交易者改这几个数就能改变种群在参数空间里的搜索形态。 类里私有部分藏了 Distance、LimitSpeed、KeepWithinBounds 等函数,说明速度上限按各维度 rangeMax-rangeMin 动态算,distanceMax 则是各维速度上限平方和,直接决定邻居判定的空间尺度。 Init 函数先做 StandardInit 校验边界数组,再把 agent 数组按 popSize 扩容,每个代理调用自身 Init(coords) 完成坐标维度初始化;下面这段就是核心起步代码。 速度上限数组 speedMax 按维度赋值后,distanceMax 累加 pow(speedMax[c],2),这意味着若某维搜索区间跨度 100、另一维跨度 50,distanceMax 会落到 10000+2500=12500,邻居半径平方据此定标。外汇与贵金属杠杆品种用此类寻参时,区间设宽可能导致过拟合,回测结果倾向失真,属高风险操作。
class="type">bool C_AO_Boids::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 { if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false; class=class="str">"cmt">//---------------------------------------------------------------------------- ArrayResize(agent, popSize); for (class="type">int i = class="num">0; i < popSize; i++) agent [i].Init(coords); distanceMax = class="num">0; ArrayResize(speedMax, coords); for (class="type">int c = class="num">0; c < coords; c++) { speedMax [c] = rangeMax [c] - rangeMin [c]; distanceMax += pow(speedMax [c], class="num">2); }
「用全局变量把群体参数喂给运动函数」
这段逻辑解决的是 Boids 群体算法里「参数从哪来、何时重置」的问题。初始化收尾时,先把 distanceMax 开平方当作有效半径,再把 8 个权重与距离参数通过 GlobalVariableSet 写进全局变量,键名从 1cohesionWeight 到 8minSpeed 顺序排列,最后置 #reset 为 1.0 并 return true。 Moving() 每次被调都会先读 #reset:若为 1.0,则清空 revision 标记并把 #reset 改回 0.0,随后从全局变量把 8 个参数逐个读回本地成员。这样 EA 重启或外部面板改参后,群体能立刻吃到新值,而不用重编译。 当 revision 为 false 时,代码用两层 for 给 popSize 个 agent 在 coords 维上随机撒点:位置取 rangeMin~rangeMax 均匀随机,速度取区间跨度乘 [-1,1] 随机再乘 0.001 的微小系数,同时算好离散网格索引。初始化完置 revision=true 并直接 return,下一帧才进入正式迭代。 正式帧里对每个 agent 依次跑 CalculateMass、Cohesion、Separation、Alignment,再用 LimitSpeed 限住速度、KeepWithinBounds 防越界,最后位置按 dx 累加并更新网格索引。外汇与贵金属市场波动剧烈,这类模拟仅作形态参考,实盘使用群体信号须自担高风险。
distanceMax = sqrt(distanceMax); GlobalVariableSet("class="macro">#reset", class="num">1.0); GlobalVariableSet("1cohesionWeight", params [class="num">1].val); GlobalVariableSet("2cohesionDist", params [class="num">2].val); GlobalVariableSet("3separationWeight", params [class="num">3].val); GlobalVariableSet("4separationDist", params [class="num">4].val); GlobalVariableSet("5alignmentWeight", params [class="num">5].val); GlobalVariableSet("6alignmentDist", params [class="num">6].val); GlobalVariableSet("7maxSpeed", params [class="num">7].val); GlobalVariableSet("8minSpeed", params [class="num">8].val); class="kw">return true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_Boids::Moving() { class="type">class="kw">double reset = GlobalVariableGet("class="macro">#reset"); if (reset == class="num">1.0) { revision = class="kw">false; GlobalVariableSet("class="macro">#reset", class="num">0.0); } cohesionWeight = GlobalVariableGet("1cohesionWeight"); cohesionDist = GlobalVariableGet("2cohesionDist"); separationWeight = GlobalVariableGet("3separationWeight"); separationDist = GlobalVariableGet("4separationDist"); alignmentWeight = GlobalVariableGet("5alignmentWeight"); alignmentDist = GlobalVariableGet("6alignmentDist"); maxSpeed = GlobalVariableGet("7maxSpeed"); minSpeed = GlobalVariableGet("8minSpeed"); if (!revision) { for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { agent [i].x [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI(-class="num">1.0, class="num">1.0) * class="num">0.001; a [i].c [c] = u.SeInDiSp(agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- for (class="type">int i = class="num">0; i < popSize; i++) { CalculateMass(); Cohesion(agent [i], i); Separation(agent [i], i); Alignment(agent [i], i); LimitSpeed(agent [i]); KeepWithinBounds(agent [i]); for (class="type">int c = class="num">0; c < coords; c++) { agent [i].x [c] += agent [i].dx [c]; a [i].c [c] = u.SeInDiSp(agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } }
◍ 群体智能里的三类作用力拆解
把 Boids 模型搬进 MT5 做行情聚类时,核心就是给每个 agent 算三种局部力:聚合、分离、对齐。下面这段实现里,CalculateMass 先扫一遍种群,用 DBL_MAX 初始化找出适应度极值,再把每个 agent 的 f 线性映射到 0~1 区间作为质量 m,质量越大在后续加权里话语权越高。 聚合力 Cohesion 只拉拢半径 distanceMax * cohesionDist 内的邻居,把它们的坐标累加求平均中心,再让当前 boid 朝中心偏移,权重由 cohesionWeight 控制。分离力 Separation 则在 distanceMax * separationDist 内做互斥,直接把「自己减邻居」的坐标差乘 separationWeight 加进 dx,避免 agent 叠在一起。 对齐 Alignment 取邻域内其他 agent 的速度向量 dx 均值,让当前 boid 向群体平均速度靠拢。三种力的半径都挂在 distanceMax 上,调参时改一个基准距离就能整体缩放邻域灵敏度;外汇与贵金属波动跳变快,这类模型在高杠杆下误触发可能放大滑点,实盘前务必在策略测试器里用历史数据验证邻域参数。 代码里 numNeighbors 为 0 时聚合与对齐会触发除零,MT5 跑出来是 INF 而非报错,复制去用时记得加一句保护。
class="type">void C_AO_Boids::CalculateMass() { class="type">class="kw">double maxMass = -DBL_MAX; class="type">class="kw">double minMass = DBL_MAX; for (class="type">int i = class="num">0; i < popSize; i++) { if (a [i].f > maxMass) maxMass = a [i].f; if (a [i].f < minMass) minMass = a [i].f; } for (class="type">int i = class="num">0; i < popSize; i++) { agent [i].m = u.Scale(a [i].f, minMass, maxMass, class="num">0.0, class="num">1.0); } } class="type">void C_AO_Boids::Cohesion(S_Boids_Agent &boid, class="type">int pos) { class="type">class="kw">double centerX []; ArrayResize(centerX, coords); ArrayInitialize(centerX, class="num">0.0); class="type">int numNeighbors = class="num">0; for (class="type">int i = class="num">0; i < popSize; i++) { if (pos != i) { if (Distance(boid, agent [i]) < distanceMax * cohesionDist) { for (class="type">int c = class="num">0; c < coords; c++) { centerX [c] += agent [i].x [c]; } numNeighbors++; } } } for (class="type">int c = class="num">0; c < coords; c++) { centerX [c] /= numNeighbors; boid.dx [c] += (centerX [c] - boid.x [c]) * cohesionWeight; } } class="type">void C_AO_Boids::Separation(S_Boids_Agent &boid, class="type">int pos) { class="type">class="kw">double moveX []; ArrayResize(moveX, coords); ArrayInitialize(moveX, class="num">0.0); for (class="type">int i = class="num">0; i < popSize; i++) { if (pos != i) { if (Distance(boid, agent [i]) < distanceMax * separationDist) { for (class="type">int c = class="num">0; c < coords; c++) { moveX [c] += boid.x [c] - agent [i].x [c]; } } } } for (class="type">int c = class="num">0; c < coords; c++) { boid.dx [c] += moveX [c] * separationWeight; } } class="type">void C_AO_Boids::Alignment(S_Boids_Agent &boid, class="type">int pos) { class="type">class="kw">double avgDX []; ArrayResize(avgDX, coords); ArrayInitialize(avgDX, class="num">0.0); class="type">int numNeighbors = class="num">0; for (class="type">int i = class="num">0; i < popSize; i++) { if (pos != i) { if (Distance(boid, agent [i]) < distanceMax * alignmentDist) { for (class="type">int c = class="num">0; c < coords; c++) { avgDX [c] += agent [i].dx [c];
对齐、限速与边界反弹的实现细节
这段群体算法里,对齐阶段先把邻域个体的平均速度 avgDX 按邻居数归一,再拿 alignmentWeight 做加权修正:boid.dx[c] += (avgDX[c] - boid.dx[c]) * alignmentWeight,权重调大,群体方向收敛更快,但容易在震荡行情里集体追错边。 LimitSpeed 先求当前速度模长 speed = sqrt(∑dx²),随后用 (rangeMax[c]-rangeMin[c])*minSpeed 算出最小速度阈值 d。若某维速度绝对值低于 d,就钳到 ±d,避免 agent 在价格区间内近乎静止;上限则乘 speedMax[c]*maxSpeed,外汇与贵金属杠杆高,这种硬限速能压住单维跳变带来的虚假信号。 KeepWithinBounds 已弃用软边界注释块,改成越界即反向:boid.dx[c] *= -1,turnFactor 留作 (rangeMax[c]-rangeMin[c])*0.0001 的潜在回调系数。注意 Distance 函数里有笔误——pow(boid1.x[c]-boid1.x[c],2) 永远为 0,应改为 boid1.x[c]-boid2.x[c],否则邻域距离全算成 0,对齐逻辑会失效。 Revision 每代扫一遍种群,抓到优于历史最优 fB 的个体就 ArrayCopy 进 cB;ind 初始 -1 作未更新标记,MT5 里直接跑能验证最优解是否被及时记录。
}
numNeighbors++;
}
}
}
for (class="type">int c = class="num">0; c < coords; c++)
{
avgDX [c] /= numNeighbors;
boid.dx [c] += (avgDX [c] - boid.dx [c]) * alignmentWeight;
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_Boids::LimitSpeed(S_Boids_Agent &boid)
{
class="type">class="kw">double speed = class="num">0;
for (class="type">int c = class="num">0; c < coords; c++)
{
speed += boid.dx [c] * boid.dx [c];
}
speed = sqrt(speed);
class="type">class="kw">double d = class="num">0;
for (class="type">int c = class="num">0; c < coords; c++)
{
d = (rangeMax [c] - rangeMin [c]) * minSpeed;
boid.dx [c] = (boid.dx [c] / speed) * speedMax [c] * maxSpeed;
if (fabs(boid.dx [c]) < d)
{
if (boid.dx [c] < class="num">0.0) boid.dx [c] = -d;
else boid.dx [c] = d;
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_Boids::KeepWithinBounds(S_Boids_Agent &boid)
{
for (class="type">int c = class="num">0; c < coords; c++)
{
class="type">class="kw">double margin = class="num">0; class=class="str">"cmt">//(rangeMax [c] - rangeMin [c])* class="num">0.00001;
class="type">class="kw">double turnFactor = (rangeMax [c] - rangeMin [c]) * class="num">0.0001;
class=class="str">"cmt">/*
if (boid.x [c] < rangeMin [c] + margin)
{
boid.dx [c] += turnFactor;
}
if (boid.x [c] > rangeMax [c] - margin)
{
boid.dx [c] -= turnFactor;
}
*/
if (boid.x [c] < rangeMin [c])
{
class=class="str">"cmt">//boid.x [c] = rangeMax [c];
boid.dx [c] *= -class="num">1;
}
if (boid.x [c] > rangeMax [c])
{
class=class="str">"cmt">//boid.x [c] = rangeMin [c];
boid.dx [c] *= -class="num">1;
}
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_Boids::Distance(S_Boids_Agent &boid1, S_Boids_Agent &boid2)
{
class="type">class="kw">double dist = class="num">0;
for (class="type">int c = class="num">0; c < coords; c++)
{
dist += pow(boid1.x [c] - boid1.x [c], class="num">2);
}
class="kw">return sqrt(dist);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_Boids::Revision()
{
class="type">int ind = -class="num">1;
for (class="type">int i = class="num">0; i < popSize; i++)
{
if (a [i].f > fB) ind = i;
}
if (ind != -class="num">1)
{
fB = a [ind].f;
ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY);
}
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————「Boids跑分实测:效率仅到天花板四分之一」
把Boids塞进Hilly、Forest、Megacity三组标准测试函数里各跑10万次,得分随问题规模放大反而塌得更快:5个Hilly得0.433,25个掉到0.306,500个只剩0.254;Megacity从0.278一路滑到0.098。三项加总2.22889,刚好是最大可能分9的24.77%,说明这套仿鸟群逻辑在通用优化里上限偏低。 横向拉一张33种算法的排名表,Boids排第28,总分2.229,紧挨着它的集群智能兄弟PSO(2.230,也是24.77%)。前排的BGA拿6.921、DE拿4.955,差距不是调参能抹平的。 在MT5里如果你打算用Boids类思路做参数寻优,建议先拿小样本函数验证——外汇与贵金属市场波动结构更复杂,直接套这套低分算法,过拟合和失效的概率倾向偏高。
◍ 把这条线请下神坛
回测里 Boids 在 1000 变量的 Forest 与 Megacity 函数上评级逼近 0.99,和白表上半区算法不相上下,说明高维空间里它未必一无是处。 但 Hilly 这类平滑函数上它扩展性差,收敛低、计算重,别在 MT5 优化器里把它当万能钥匙。 外汇与贵金属参数寻优属高风险操作,算法结果仅作概率参考;真要试,先拿 ZIP 里的 28.61 KB 源码在小样本跑通再放大。