大逃杀优化器(BRO)·进阶篇
「BRO算法的种群对抗与空间收缩机制」
大逃杀优化(BRO)把种群当成竞技场:每个解在指定区间内随机生成坐标,迭代中若碰到更优解就刷新全局最优。解之间两两找最近邻「对战」,胜者伤害计数器清零,败者伤害加1并向全局最优移动;当某个解伤害累计到 maxDamage 阈值,直接淘汰并用随机解替换。这种机制让劣势解持续被挤压出局,种群整体向高适应度区迁移。 搜索空间不是一成不变的。原文用 δ = ⌊总迭代 / log₁₀(总迭代)⌋ 控制收缩节奏,每过 δ 轮就按种群各维标准差重定边界:新下限 = 最优解 − 标准差,新上限 = 最优解 + 标准差。曲线显示 δ 随迭代增大但远慢于线性,前期收缩频、后期收缩疏,兼顾探索与精细化。我在实验里改用两次对数 δ = ⌊epochs / log(log(epochs))⌋,实测聚焦更快。 代码层面对应一个继承 C_AO 的 C_AO_BRO 类。popSize 默认100、maxDamage 默认3,SetParams() 支持运行时改这两个值;Init() 调 StandardInit 并初始化 damages 数组,delta 算错时兜底为1。下面这段是 δ 初始化与类骨架,注意 ao_link 只是内部引用,实盘用不到。 [CODE] // Calculate the initial delta to narrow the search space delta = (int)MathFloor(epochs / MathLog(MathLog(epochs))); //—————————————————————————————————————————————————————————————————————————————— class C_AO_BRO : public C_AO { public: //-------------------------------------------------------------------- ~C_AO_BRO () { } C_AO_BRO () { ao_name = "BRO"; ao_desc = "Battle Royale Optimizer"; ao_link = "[MQL5官方文档] popSize = 100; // population size maxDamage = 3; // maximum damage threshold ArrayResize (params, 2); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "maxDamage"; params [1].val = maxDamage; } void SetParams () { popSize = (int)params [0].val; maxDamage = (int)params [1].val; } 逐行拆解:第1行注释说明下方算搜索空间收缩间隔 δ;第2行用 MathFloor 对 epochs/log(log(epochs)) 向下取整赋给 delta,双对数让收缩更缓。class 行定义公开继承 C_AO 的 BRO 类;析构为空。构造函数里先写算法名与描述,ao_link 保留出处链接;popSize 设100、maxDamage 设3;ArrayResize 把参数数组扩到2,分别绑 popSize 与 maxDamage 初值。SetParams 从 params 读值回写成员变量,使 EA 运行中可动态调参。 外汇与贵金属市场高杠杆、高波动,这类元启发式优化仅用于离线调参或信号研究,实盘前务必在 MT5 策略测试器用历史数据验证,任何参数组合都不保证盈利。
class=class="str">"cmt">// Calculate the initial delta to narrow the search space delta = (class="type">int)MathFloor(epochs / MathLog(MathLog(epochs))); class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class C_AO_BRO : class="kw">public C_AO { class="kw">public: class=class="str">"cmt">//-------------------------------------------------------------------- ~C_AO_BRO() { } C_AO_BRO() { ao_name = "BRO"; ao_desc = "Battle Royale Optimizer"; ao_link = "[MQL5官方文档] popSize = class="num">100; class=class="str">"cmt">// population size maxDamage = class="num">3; class=class="str">"cmt">// maximum damage threshold ArrayResize(params, class="num">2); params [class="num">0].name = "popSize"; params [class="num">0].val = popSize; params [class="num">1].name = "maxDamage"; params [class="num">1].val = maxDamage; } class="type">void SetParams() { popSize = (class="type">int)params [class="num">0].val; maxDamage = (class="type">int)params [class="num">1].val; }
群体优化算法的初始化与游走逻辑
下面这段类声明和 Init 实现,来自一套基于群体搜索的布林带类指标优化框架(C_AO_BRO)。它的核心不是直接算指标,而是先圈定参数搜索空间,再用「损伤计数 + 空间收缩」去逼近较优解。外汇与贵金属波动率高,这类自动寻参仅降低人工试错成本,不承诺任何胜率。 Init 方法里有个关键量 delta:用 epochs 除以 log10(epochs) 向下取整得到收缩间隔,若结果 ≤0 则强制为 1。例如 epochs=1000 时,delta = floor(1000/3) ≈ 333,意味着每过约 333 代才缩一次搜索范围,避免过早陷入局部最优。 Moving 方法负责种群游走:当 revision 标志为假时,用双重循环给 popSize 个个体、coords 个坐标做随机初始化。这一步就是「盲搜」起点,后续 Revision 才根据 damages 数组淘汰差解。 开 MT5 把下面代码贴进自定义类的 .mqh,改 popSize 和 epochs 跑一遍,能直观看到 delta 随 epochs 变化的非线性节奏。
class="type">bool 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">// search step class="kw">const class="type">int epochsP = class="num">0); class=class="str">"cmt">// number of epochs class="type">void Moving(); class="type">void Revision(); class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">int maxDamage; class=class="str">"cmt">// maximum damage threshold class="kw">private: class=class="str">"cmt">//------------------------------------------------------------------- class="type">int delta; class=class="str">"cmt">// interval for shrinking the search space class="type">int damages []; class=class="str">"cmt">// amount of damage for each solution class="type">int epoch; class=class="str">"cmt">// current epoch class="type">int epochs; class=class="str">"cmt">// maximum number of epochs class=class="str">"cmt">// Auxiliary methods class="type">int FindNearestNeighbor(class="type">int index); class="type">class="kw">double CalculateDistance(class="type">int idx1, class="type">int idx2); class="type">void CalculateStandardDeviations(class="type">class="kw">double &sdValues []); class="type">void ShrinkSearchSpace(); }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_BRO::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">// search step 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 false; class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">// Initialize damage counters for each solution ArrayResize(damages, popSize); ArrayInitialize(damages, class="num">0); class=class="str">"cmt">// Set epochs epochs = epochsP; epoch = class="num">0; class=class="str">"cmt">// Calculate the initial &class="macro">#x27;delta&class="macro">#x27; to narrow the search space delta = (class="type">int)MathFloor(epochs / MathLog10(epochs)); if (delta <= class="num">0) delta = class="num">1; class="kw">return true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— /—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_BRO::Moving() { if (!revision) { class=class="str">"cmt">// Initialize the population with random decisions for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) {
◍ 败者向最优解靠拢的迭代机制
Revision() 是群体优化里每轮必跑的更新函数,先 epoch++ 再刷新全局最优:遍历 popSize 个解,只要某个体适应度 a[i].f 大于已知最优 fB,就把它的坐标数组拷进 cB,fB 同步抬高。这一步保证历史最好解不丢失,是后续所有移动的锚点。 紧接着做近邻对抗:FindNearestNeighbor(i) 找出空间上最近的对手,若 a[i].f >= a[neighbor].f,i 胜出、自身 damages 清零而对手 damages++,败者按 r=u.RNDfromCI(0,1) 的比例朝 cB 插值移动,再经 SeInDiSp 钳回参数边界与步长。反向情况对称处理,i 败则自己朝最优解挪。 当某个解的 damages 累计到 maxDamage,直接整组坐标重掷为 [rangeMin,rangeMax] 内的随机点并重置计数器,避免种群早收敛在局部洼地。若设了 epochs>0 且 epoch % delta == 0,则调用 ShrinkSearchSpace() 周期性收窄搜索域,搜索粒度随时间收紧,后期更倾向精细 exploitation 而非盲目 exploration。 在 MT5 里把 maxDamage 设小(如 3~5)、delta 设 20~50,能明显加快收敛但过拟合风险升高;外汇与贵金属波动剧烈,这类参数寻优结果仅代表历史样本倾向,实盘仍需严控仓位。
class="type">void C_AO_BRO::Revision() { epoch++; class=class="str">"cmt">// Update the global best solution for (class="type">int i = class="num">0; i < popSize; i++) { if (a [i].f > fB) { fB = a [i].f; ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } } class=class="str">"cmt">// Compare each solution with its nearest neighbor and update damage counters for (class="type">int i = class="num">0; i < popSize; i++) { class="type">int neighbor = FindNearestNeighbor(i); if (neighbor != -class="num">1) { if (a [i].f >= a [neighbor].f) { class=class="str">"cmt">// Solution i wins damages [i] = class="num">0; damages [neighbor]++; class=class="str">"cmt">// The loser(neighbor) moves toward the best solution for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double r = u.RNDfromCI(class="num">0, class="num">1); a [neighbor].c [c] = a [neighbor].c [c] + r * (cB [c] - a [neighbor].c [c]); a [neighbor].c [c] = u.SeInDiSp(a [neighbor].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } else { class=class="str">"cmt">// Solution i loses damages [i]++; damages [neighbor] = class="num">0; class=class="str">"cmt">// The loser(i) moves to the best solution for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double r = u.RNDfromCI(class="num">0, class="num">1); a [i].c [c] = a [i].c [c] + r * (cB [c] - a [i].c [c]); a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } } } class=class="str">"cmt">// Check if any solution has reached maximum damage and replace it for (class="type">int i = class="num">0; i < popSize; i++) { if (damages [i] >= maxDamage) { class=class="str">"cmt">// Reset the damage counter damages [i] = class="num">0; class=class="str">"cmt">// Generate a new random solution for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double coordinate = u.RNDfromCI(rangeMin [c], rangeMax [c]); a [i].c [c] = u.SeInDiSp(coordinate, rangeMin [c], rangeMax [c], rangeStep [c]); } } } class=class="str">"cmt">// Periodic narrowing of the search space if (epochs > class="num">0 && epoch % delta == class="num">0) { ShrinkSearchSpace(); class=class="str">"cmt">// Update delta
「邻域检索与搜索空间收缩的实现细节」
黑猩猩优化器里「找最近邻」不是靠暴力比对全部个体,而是用欧氏距离在多维坐标上筛。FindNearestNeighbor 从 0 扫到 popSize,跳过自身后取最小 distance 对应的下标,返回 nearestIndex;这套逻辑在群体规模 50 时大约要算 49×coords 次差值,MT5 单 tick 内跑完无压力。 CalculateDistance 只做一件事:把两个个体在第 c 维的坐标差平方累加,最后开根号。注意它没除以维度做归一,所以当 coords 很大时距离数值会随维度线性膨胀,调参时得留意边界爆 double。 ShrinkSearchSpace 是每代收束的核心。先算各维标准差 sdValues,再以当前最优 cB[c] 为中心、正负一个标准差重设 rangeMin/rangeMax,越界就夹回初始约束。外汇与贵金属波动率高,这种收缩若迭代过快可能过早陷在局部优解,实盘前建议在策略测试器里把 popSize 拉到 80 以上观察收敛曲线。
delta = delta + (class="type">int)MathRound(delta / class="num">2); } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">int C_AO_BRO::FindNearestNeighbor(class="type">int index) { class="type">class="kw">double minDistance = DBL_MAX; class="type">int nearestIndex = -class="num">1; for (class="type">int i = class="num">0; i < popSize; i++) { if (i == index) class="kw">continue; class="type">class="kw">double distance = CalculateDistance(index, i); if (distance < minDistance) { minDistance = distance; nearestIndex = i; } } class="kw">return nearestIndex; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">class="kw">double C_AO_BRO::CalculateDistance(class="type">int idx1, class="type">int idx2) { class="type">class="kw">double distanceSum = class="num">0.0; for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double diff = a [idx1].c [c] - a [idx2].c [c]; distanceSum += diff * diff; } class="kw">return MathSqrt(distanceSum); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">class="kw">double C_AO_BRO::CalculateStandardDeviations(class="type">class="kw">double &sdValues []) { ArrayResize(sdValues, coords); for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double sum = class="num">0.0; class="type">class="kw">double mean = class="num">0.0; class=class="str">"cmt">// Calculate the average for (class="type">int i = class="num">0; i < popSize; i++) mean += a [i].c [c]; mean /= popSize; class=class="str">"cmt">// Calculate the sum of squared deviations for (class="type">int i = class="num">0; i < popSize; i++) { class="type">class="kw">double diff = a [i].c [c] - mean; sum += diff * diff; } sdValues [c] = MathSqrt(sum / popSize); } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_BRO::ShrinkSearchSpace() { class="type">class="kw">double sdValues []; CalculateStandardDeviations(sdValues); for (class="type">int c = class="num">0; c < coords; c++) { class=class="str">"cmt">// The new boundaries are centered around the best solution with a standard deviation width class="type">class="kw">double newMin = cB [c] - sdValues [c]; class="type">class="kw">double newMax = cB [c] + sdValues [c]; class=class="str">"cmt">// Make sure the new bounds are within the original constraints if (newMin < rangeMin [c]) newMin = rangeMin [c]; if (newMax > rangeMax [c]) newMax = rangeMax [c]; class=class="str">"cmt">// Update the boundaries rangeMin [c] = newMin; rangeMax [c] = newMax; } } class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————