种群优化算法:二进制遗传算法(BGA)。第 II 部分(基础篇)
📘

种群优化算法:二进制遗传算法(BGA)。第 II 部分(基础篇)

第 1/3 篇

◍ 二进制遗传算法在 MT5 里的落地骨架

二进制遗传算法(BGA)把连续搜索空间映射成 0/1 串,再用选择、交叉、变异迭代逼近较优解。在 MT5 中跑这套,核心是把指标参数或仓位规则编码成定长染色体,让 EA 在回测里自行淘汰弱势个体。 作者 Andrey Dik 在 2024 年 10 月 21 日发布的示例里,原文标注了 720 次浏览与 1 条讨论,说明该系列偏冷门但可复现。打开 MT5 终端的「文件→打开数据文件夹→MQL5/Experts」,把 BGA 框架丢进去编译,就能看到种群在每一代把适应度函数往回测盈利方向推。 外汇与贵金属品种波动跳空频繁,用 BGA 优化参数存在过拟合倾向,实盘前务必用样本外数据交叉验证,高风险品种轻仓试错。

「二进制遗传算法的来路与表达形式」

二进制遗传算法(BGA)是遗传算法家族里最早成型的变体,核心特征只有一个:把每个解元写成 0/1 比特串,再用交叠和突变在串上操作产生新一代。它归在进化算法大类下,和遗传编程、进化策略共享“种群+遗传算子”的骨架。 这套思路并非凭空出现。John Holland 在 1975 年出版《自然和人工系统中的适应》把 GA 作为整体优化方向提出,BGA 则把达尔文的自然选择、基因染色体概念直接映射成比特位:串里每个比特组是一条基因,代表一个待优化参数。 一个可验证的落点:MetaTrader 5 平台自身就采用二进制 GA 做优化,所以你在 MT5 策略测试器的遗传优化开关背后,跑的就是这类比特串搜索。传统解析法搞不定的复杂曲面,BGA 靠种群迭代有可能找到可用解,但外汇/贵金属参数面高度噪声,结果仅具概率倾向。 顺带一提,BGA 不止用于交易参数寻优,也能进化神经网络结构——它本质是借自然选择原理搜解,而非保证收敛到全局最优。

二进制遗传算法的染色体与参数落地

二进制遗传算法(BGA)在 MT5 优化器里跑的是一套固定流水线:随机初始化种群、算适应度、轮盘选亲本、交叠断点不限于基因边界、颠倒与突变、再按适应度排序并入总群。原文用 max/min 距离约束编码,只产出正数,并以格雷码塞进一条字符数组染色体,断点可落在基因内部而非连接点,这一点直接决定了交叉算子的粒度。 默认外部参数基本是多数文献的共识值,作者在多任务回测里筛选过:种群大小 50 在多样性与收敛速度间较平衡;亲本数量 50,调小利于平滑函数精度、调大利于离散函数素材多样性;交叠概率 1.0 组合力强但降速会卡顿;交叠点 3;突变概率 0.001 过低丢多样性、过高丢信息;颠倒概率 0.7 过高会随机化;小数位超 16 在转双精度时比特会丢。单独 10~50 参数函数上乱偏这些数可能 100% 收敛,但其它任务效率会明显掉,实盘外汇/贵金属优化属高风险,参数偏离须自行回测验证。 基因用 S_BinaryGene 结构承载:gene 为二进制字符数组,integ/fractPart 分整数与小数格雷段,Init 按范围与小数位算最大实数,ToDouble 从染色体切片还原实数并缩放。个体 S_Agent 聚合 genes 与 chromosome,ExtractGenes 批量转坐标。C_AO_BGA 类靠 Init 分配内存、Moving 跑主循环、Revision 排序选优。 下面这段是格雷码最大值的求法,digitsInGrayCode=3 时返回 7(二进制 111),是染色体定长的底层依赖: 代码逐行拆解: ulong GetMaxDecimalFromGray — 返回用指定位数格雷码能编码的最大 ulong。 maxValue=1 — 从 1 起步(至少占 1 位)。 for(i=1;i<digitsInGrayCode;i++) — 从第二位开始补位。 maxValue<<=1 — 左移腾出一位。 maxValue|=1 — 该位补 1,构造全 1 格雷上限。 return maxValue — 全 1 即该位宽最大值。 S_BinaryGene 结构里 gene/integPart/fractPart 都是 char 数组,分别存整条基因与整数、小数部分的二进制片段,后续 ToDouble 按 integGrayDigits 与 fractGrayDigits 截取还原。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="comment">class=class="str">"cmt">//Calculation of the maximum possible class="type">ulong number using the Gray code for a given number of bits</span>
<span class="keyword">class="type">ulong</span> GetMaxDecimalFromGray(<span class="keyword">class="type">int</span> digitsInGrayCode)
{
&nbsp;&nbsp;<span class="keyword">class="type">ulong</span> maxValue = <span class="number">class="num">1</span>;
&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">1</span>; i &lt; digitsInGrayCode; i++)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;maxValue &lt;&lt;= <span class="number">class="num">1</span>;
&nbsp;&nbsp;&nbsp;&nbsp;maxValue |= <span class="number">class="num">1</span>;
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="keyword">class="kw">return</span> maxValue;
}
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class="kw">struct</span> S_BinaryGene
{
&nbsp;&nbsp;<span class="keyword">class="type">char</span>&nbsp;&nbsp; gene&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [];
&nbsp;&nbsp;<span class="keyword">class="type">char</span>&nbsp;&nbsp; integPart [];
&nbsp;&nbsp;<span class="keyword">class="type">char</span>&nbsp;&nbsp; fractPart [];

◍ 把染色体段译回浮点参数

遗传算法跑完之后,最麻烦的一步是把二进制染色体翻译回真实可用的 double 参数。这段逻辑用 Gray 码分段存整数部和小数部,避开了普通二进制翻转时相邻值跳变过大的坑。 Init 里先按传入的小数位数 doubleDigitsInChromo 算 doubleFract = 0.1^位数,例如 3 位小数就是 0.001;随后把 max-min 的区间长度做 DecimalToGray,分别得到整数部与小数部的 Gray 数组长度,两者相加就是单条基因总长 length。 ToDouble 是解码核心:从染色体 indChr 偏移处取 integGrayDigits 长度还原整数,再取 fractGrayDigits 长度乘 doubleFract 得小数,最后用 Scale 把 [0, maxDoubleNumber] 线性映射到 [min, max]。外汇与贵金属参数空间优化属高风险实验,映射偏差可能让 EA 在实盘表现与回测背离。 直接把下面代码丢进 MT5 的 EA 头文件,改 doubleDigitsInChromo 就能控精度;染色体长度随位数线性增长,4 位小数比 2 位多出约一倍基因位,种群规模得同步加。

MQL5 / C++
class="type">uint   integGrayDigits;
class="type">uint   fractGrayDigits;
class="type">uint   length;
class="type">class="kw">double minDoubleRange;
class="type">class="kw">double maxDoubleRange;
class="type">class="kw">double maxDoubleNumber;
class="type">class="kw">double doubleFract;
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">void Init(class="type">class="kw">double min, class="type">class="kw">double max, class="type">int doubleDigitsInChromo)
{
  doubleFract = pow(class="num">0.1, doubleDigitsInChromo);
  
  minDoubleRange = min;
  maxDoubleRange = max - min;
  class="type">ulong decInfr = class="num">0;
  for (class="type">int i = class="num">0; i < doubleDigitsInChromo; i++)
  {
    decInfr += class="num">9 * (class="type">ulong)pow(class="num">10, i);
  }
  
  class=class="str">"cmt">//----------------------------------------
  DecimalToGray(decInfr, fractPart);
  
  class="type">ulong maxDecInFr = GetMaxDecimalFromGray(ArraySize(fractPart));
  class="type">class="kw">double maxDoubFr = maxDecInFr * doubleFract;
  
  
  class=class="str">"cmt">//----------------------------------------
  DecimalToGray((class="type">ulong)maxDoubleRange, integPart);
  
  class="type">ulong  maxDecInInteg = GetMaxDecimalFromGray(ArraySize(integPart));
  class="type">class="kw">double maxDoubInteg  = (class="type">class="kw">double)maxDecInInteg + maxDoubFr;
  maxDoubleNumber = maxDoubInteg;
  ArrayResize(gene, class="num">0, class="num">1000);
  integGrayDigits = ArraySize(integPart);
  fractGrayDigits = ArraySize(fractPart);
  length          = integGrayDigits + fractGrayDigits;
  ArrayCopy(gene, integPart, class="num">0,               class="num">0, WHOLE_ARRAY);
  ArrayCopy(gene, fractPart, ArraySize(gene), class="num">0, WHOLE_ARRAY);
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double ToDouble(const class="type">char &chromo [], const class="type">int indChr)
{
  class="type">class="kw">double d;
  if(integGrayDigits > class="num">0)d = (class="type">class="kw">double)GrayToDecimal(chromo, indChr, indChr + integGrayDigits - class="num">1);
  else                   d = class="num">0.0;
  d +=(class="type">class="kw">double)GrayToDecimal(chromo, indChr + integGrayDigits, indChr + integGrayDigits + fractGrayDigits - class="num">1) * doubleFract;
  class="kw">return minDoubleRange + Scale(d, class="num">0.0, maxDoubleNumber, class="num">0.0, maxDoubleRange);
}
class=class="str">"cmt">//----------------------------------------------------------------------------
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)
{
  if (OutMIN == OutMAX) class="kw">return (OutMIN);
  if (InMIN == InMAX) class="kw">return (class="type">class="kw">double((OutMIN + OutMAX) / class="num">2.0));
  else
  {
    if (In < InMIN) class="kw">return OutMIN;
    if (In > InMAX) class="kw">return OutMAX;

常见问题

把染色体按段切分,每段二进制串按位权转成十进制整数,再线性映射到参数上下界区间即可还原为浮点参数。
二进制编码交叉变异实现简单、易避非法值,但精度受位数限制;实数编码精度高,选哪种看你对参数粒度与计算成本的要求。
小布盯盘的 AIGC 可内置这类诊断,打开对应品种页即可调用算法跑参数组合并给出概率倾向,你只管看结果决策。
会。位数为 n 时仅能表达 2^n 个离散点,段长不够会限制参数分辨率,建议先按所需精度反推最少位数。
种群多样性掉太快、变异率过低都可能早熟;可调高变异概率或引入精英保留与多种群机制来缓解。