基于生物地理学的优化算法(BBO)·进阶篇
(2/3)· 当 HSI 与迁徙率接管参数寻优,多数交易者还停留在把随机搜索当万能钥匙的阶段
◍ 生物地理优化算法的结构体与初始化落点
在 MT5 里用生物地理优化(BBO)做参数寻优时,先得把栖息地(habitat)相关的状态塞进一个结构体。下面这段声明定义了每个栖息地的物种数、迁入率、迁出率和存在概率,外加全局的物种上限与精英保留数,是后续种群迭代的底层数据骨架。
class="type">class="kw">double emigrationMax; class=class="str">"cmt">// 最大迁出率 class="type">class="kw">double mutationProb; class=class="str">"cmt">// 变异概率 class="type">int elitismCount; class=class="str">"cmt">// 精英解数量 class="type">int speciesMax; class=class="str">"cmt">// 物种最大数量 class="kw">private: class=class="str">"cmt">//--------------------------------------------------------------- class="kw">struct S_HabitatData { class="type">int speciesCount; class=class="str">"cmt">// 栖息地内物种数 class="type">class="kw">double immigration; class=class="str">"cmt">// 迁入率 class="type">class="kw">double emigration; class=class="str">"cmt">// 迁出率 class="type">class="kw">double probability; class=class="str">"cmt">// 存在概率 }; S_HabitatData habitatData []; class=class="str">"cmt">// 每个栖息地的数据 class="type">class="kw">double probabilities []; class=class="str">"cmt">// 统计变异用的概率数组 class="type">void InitializePopulation(); class="type">void CalculateRates(); class="type">void Migration(); class="type">void Mutation(); class="type">class="kw">double CalculateProbability(class="type">int speciesCount); };
class="type">bool C_AO_BBO::Init(const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">// 最小值 const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">// 最大值 const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// 步长 const class="type">int epochsP = class="num">0) class=class="str">"cmt">// 迭代轮数 { if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return false; ArrayResize(habitatData, popSize); ArrayResize(probabilities, speciesMax + class="num">1); class="type">class="kw">double sum = class="num">0.0; for (class="type">int i = class="num">0; i <= speciesMax; i++) { probabilities [i] = CalculateProbability(i); sum += probabilities [i]; } if (sum > class="num">0) { for (class="type">int i = class="num">0; i <= speciesMax; i++) { probabilities [i] /= sum; } } class="kw">return true; }
class="type">class="kw">double emigrationMax; class=class="str">"cmt">// maximum emigration rate class="type">class="kw">double mutationProb; class=class="str">"cmt">// mutation probability class="type">int elitismCount; class=class="str">"cmt">// number of elite solutions class="type">int speciesMax; class=class="str">"cmt">// maximum number of species class="kw">private: class=class="str">"cmt">//------------------------------------------------------------------- class="kw">struct S_HabitatData { class="type">int speciesCount; class=class="str">"cmt">// number of species in the habitat class="type">class="kw">double immigration; class=class="str">"cmt">// immigration rate class="type">class="kw">double emigration; class=class="str">"cmt">// emigration rate class="type">class="kw">double probability; class=class="str">"cmt">// probability of existence }; S_HabitatData habitatData []; class=class="str">"cmt">// data for each habitat class="type">class="kw">double probabilities []; class=class="str">"cmt">// probabilities for counting mutations class="type">void InitializePopulation(); class="type">void CalculateRates(); class="type">void Migration(); class="type">void Mutation(); class="type">class="kw">double CalculateProbability(class="type">int speciesCount); }; class="type">bool C_AO_BBO::Init(const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">// minimum values const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">// maximum values const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// step change const class="type">int epochsP = class="num">0) class=class="str">"cmt">// number of epochs { if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return false; ArrayResize(habitatData, popSize); ArrayResize(probabilities, speciesMax + class="num">1); class="type">class="kw">double sum = class="num">0.0; for (class="type">int i = class="num">0; i <= speciesMax; i++) { probabilities [i] = CalculateProbability(i); sum += probabilities [i]; } if (sum > class="num">0) { for (class="type">int i = class="num">0; i <= speciesMax; i++) { probabilities [i] /= sum; } } class="kw">return true; }
生物地理优化在EA里的迭代骨架
把 BBO(生物地理优化)塞进 MT5 的 EA 框架,核心就是一个 Moving() 驱动单次迭代。首次进入靠 revision 标志位拦截,只做种群初始化然后直接 return,后续每次调用才跑真正的栖息地演化。
迭代主流程分五步:先用 u.Sorting 按适应度(HSI)给种群排序;再算每个栖息地的迁入迁出率;接着 Migration() 交换 SIV(候选解向量);然后按概率 Mutation() 扰动;最后把本次目标值 f 暂存进 fP 留给下一代比对。
InitializePopulation() 里对每个 agent 的 coords 维都用 u.RNDfromCI 在 [rangeMin, rangeMax] 内均匀撒点,并立即用 u.SeInDiSp 吸附到合法步长网格——这意味着你调 rangeStep[] 会直接改变解空间的离散密度。
CalculateRates() 采用线性迁移模型:speciesCount = speciesMax - i * speciesMax / popSize,排名越靠前(i 越小)物种数越多,迁入率随之走低。排序后第 0 号栖息地物种数恒为 speciesMax,末位则为 0,这是改迁入/迁出曲线斜率的硬支点。
别把正态当圣经:BBO 的物种数分布是确定性线性递减,不是随机高斯。若你之前用 GA 的突变逻辑套过来,迁入率公式会直接算错,建议在 MT5 里打印 habitatData[i].speciesCount 验证前几代数值。
class="type">void C_AO_BBO::Moving() { class=class="str">"cmt">// First iteration - initialization of the initial population if (!revision) { InitializePopulation(); revision = true; class="kw">return; } class=class="str">"cmt">// Main optimization class=class="str">"cmt">// class="num">1. Sort the population by HSI(fitness) class="kw">static S_AO_Agent aTemp []; ArrayResize(aTemp, popSize); u.Sorting(a, aTemp, popSize); class=class="str">"cmt">// class="num">2. Calculate immigration and emigration rates CalculateRates(); class=class="str">"cmt">// class="num">3. Migration(exchange of SIVs between habitats) Migration(); class=class="str">"cmt">// class="num">4. Probability-based mutation Mutation(); class=class="str">"cmt">// class="num">5. Save state for the next iteration for (class="type">int i = class="num">0; i < popSize; i++) { a [i].fP = a [i].f; } } class="type">void C_AO_BBO::Revision() { class=class="str">"cmt">// Find the best solution in the current population for (class="type">int i = class="num">0; i < popSize; i++) { class=class="str">"cmt">// Update the best solution if (a [i].f > fB) { fB = a [i].f; ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } } } class="type">void C_AO_BBO::InitializePopulation() { class=class="str">"cmt">// Initialize the initial population uniformly throughout the space 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 random coordinates within acceptable limits a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); class=class="str">"cmt">// Round to the nearest acceptable step a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } class=class="str">"cmt">// Initialize habitat data habitatData [i].speciesCount = class="num">0; habitatData [i].immigration = class="num">0.0; habitatData [i].emigration = class="num">0.0; habitatData [i].probability = class="num">0.0; } } class="type">void C_AO_BBO::CalculateRates() { class=class="str">"cmt">// For the linear migration model for (class="type">int i = class="num">0; i < popSize; i++) { class=class="str">"cmt">// The number of species is inversely proportional to the rank(the best solutions have more species) habitatData [i].speciesCount = speciesMax - (i * speciesMax / popSize); class=class="str">"cmt">// The rate of immigration decreases as the number of species increases
「生物地理迁移算子的代码落点」
这套优化器的栖息地间迁移,核心是把「迁入率」和「迁出率」当概率闸门用。迁入率随物种数逼近上限而衰减,公式是 immigrationMax * (1.0 - speciesCount/speciesMax);迁出率则正比于物种占比,emigrationMax * speciesCount/speciesMax。当某栖息地物种数超过 speciesMax,其存在概率直接置 0,相当于被算法淘汰。 Migration() 函数里,elitismCount 之前的精英解被 continue 跳过,不参与变异,保证最优结构不丢。对每个非精英栖息地,先以 habitatData[i].immigration 为阈值扔随机数,命中才进入逐坐标(SIV)修改;每个坐标又独立以同样迁入率为概率,决定是否真的从该栖息地的某一维抄值。 源栖息地不是随机挑的,而是按所有其他栖息地的迁出率做轮盘赌。代码先累加 sumEmigration,再掷出 roulette = RNDprobab() * sumEmigration,线性扫描累加到 cumSum 首次越过 roulette 的 j,就把 a[j].c[c] 抄给 a[i].c[c]。这一层双重概率判断,使高迁出率且非自身的栖息地更可能被选为供体,低物种数栖息地的维度被改写概率也更高。 在 MT5 里把这段直接塞进 EA 的 C_AO_BBO 类,调小 immigrationMax 会明显压低整体扰动频率,回测中种群收敛可能更慢但更稳;外汇与贵金属品种波动跳跃大,直接挂实盘属高风险,建议先用历史数据跑种群分布。
habitatData [i].immigration = immigrationMax * (class="num">1.0 - (class="type">class="kw">double)habitatData [i].speciesCount / speciesMax); class=class="str">"cmt">// The rate of emigration increases with the number of species habitatData [i].emigration = emigrationMax * (class="type">class="kw">double)habitatData [i].speciesCount / speciesMax; class=class="str">"cmt">// Probability of habitat existence if (habitatData [i].speciesCount <= speciesMax) { habitatData [i].probability = probabilities [habitatData [i].speciesCount]; } else { habitatData [i].probability = class="num">0.0; } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//+----------------------------------------------------------------------------+ class=class="str">"cmt">//| Migration(exchange of SIVs between habitats) | class=class="str">"cmt">//+----------------------------------------------------------------------------+ class="type">void C_AO_BBO::Migration() { for (class="type">int i = class="num">0; i < popSize; i++) { class=class="str">"cmt">// Skip elite solutions if (i < elitismCount) class="kw">continue; class=class="str">"cmt">// Determine whether the habitat will be modified if (u.RNDprobab() < habitatData [i].immigration) { class=class="str">"cmt">// For each coordinate(SIV) for (class="type">int c = class="num">0; c < coords; c++) { class=class="str">"cmt">// Determine whether this coordinate will be modified if (u.RNDprobab() < habitatData [i].immigration) { class=class="str">"cmt">// Select a migration source based on emigration rates class="type">class="kw">double sumEmigration = class="num">0.0; for (class="type">int j = class="num">0; j < popSize; j++) { if (j != i) sumEmigration += habitatData [j].emigration; } if (sumEmigration > class="num">0) { class=class="str">"cmt">// Roulette source selection class="type">class="kw">double roulette = u.RNDprobab() * sumEmigration; class="type">class="kw">double cumSum = class="num">0.0; for (class="type">int j = class="num">0; j < popSize; j++) { if (j != i) { cumSum += habitatData [j].emigration; if (roulette <= cumSum) { class=class="str">"cmt">// Copy SIV from habitat j to habitat i a [i].c [c] = a [j].c [c]; break; } } } } } } } } }
◍ 按生存概率反向调变异率
生物地理优化里,精英解不参与变异,直接保留进下一代;其余个体才进入扰动流程。这段代码把变异概率和该个体的存在概率挂钩:存在概率越高,变异率越低,公式写成 mutationRate = mutationProb * (1.0 - habitatData[i].probability)。 具体实现里,先用 u.RNDprobab() 抽一个随机数,小于计算出的 mutationRate 才触发变异;变异坐标用 MathRand() % coords 随机挑一个维度,新值从对应维度的区间里取,再用 SeInDiSp 对齐到步长网格。这样高适应度个体倾向稳定,低适应度个体更容易被重采样。 CalculateProbability 用了一个简化模型:物种数落在 speciesMax/2 附近时概率最高,偏离均衡点按高斯型衰减,返回 MathExp(-d²/(2·eq²))。若 speciesMax 设 100,均衡点就是 50,物种数偏离到 80 时概率约降到 0.24,偏离越远越接近 0。 在 MT5 里把这段接进自己的 EA 框架时,重点盯 mutationProb 和 rangeStep:步长过粗会让变异跳不到精细参数区,过细又拖慢收敛。外汇与贵金属杠杆高、滑点随机,任何优化结果都只是概率倾向,实盘前请用历史数据多周期回测验证。
class="type">void C_AO_BBO::Mutation() { for (class="type">int i = class="num">0; i < popSize; i++) { class=class="str">"cmt">// Skip elite solutions if (i < elitismCount) class="kw">continue; class=class="str">"cmt">// The mutation rate is inversely proportional to the probability of existence class="type">class="kw">double mutationRate = mutationProb * (class="num">1.0 - habitatData [i].probability); if (u.RNDprobab() < mutationRate) { class=class="str">"cmt">// Select a random coordinate for mutation class="type">int mutateCoord = MathRand() % coords; class=class="str">"cmt">// Generate a new value for the selected coordinate a [i].c [mutateCoord] = u.RNDfromCI(rangeMin [mutateCoord], rangeMax [mutateCoord]); a [i].c [mutateCoord] = u.SeInDiSp(a [i].c [mutateCoord], rangeMin [mutateCoord], rangeMax [mutateCoord], rangeStep [mutateCoord]); } } } class="type">class="kw">double C_AO_BBO::CalculateProbability(class="type">int speciesCount) { class=class="str">"cmt">// Simplified probability model class=class="str">"cmt">// Maximum probability in the middle of the range(equilibrium) class="type">int equilibrium = speciesMax / class="num">2; class="type">class="kw">double distance = MathAbs(speciesCount - equilibrium); class="type">class="kw">double probability = MathExp(-distance * distance / (class="num">2.0 * equilibrium * equilibrium)); class="kw">return probability; }