种群优化算法:二进制遗传算法(BGA)。第 II 部分(基础篇)
◍ 二进制遗传算法在 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 截取还原。
<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) { <span class="keyword">class="type">ulong</span> maxValue = <span class="number">class="num">1</span>; <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">1</span>; i < digitsInGrayCode; i++) { maxValue <<= <span class="number">class="num">1</span>; maxValue |= <span class="number">class="num">1</span>; } <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 { <span class="keyword">class="type">char</span> gene []; <span class="keyword">class="type">char</span> integPart []; <span class="keyword">class="type">char</span> 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 位多出约一倍基因位,种群规模得同步加。
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;