种群优化算法:二进制遗传算法(BGA)。第 I 部分(基础篇)
「用二进制编码重排交易参数搜索空间」
在 MT5 里做策略寻优,实数编码直观但容易陷入局部极值;二进制遗传算法(BGA)把每个待调参数映射成 0/1 串,再靠选择、交叠、突变三代操作逼近更优解。这种编码方式在 2024-10-14 发布的示例中被用于种群优化,原文标注示例阅读量 722、评论 5 条,说明社区对可落地的寻优骨架有真实需求。 二进制并非简单按位展开。格雷码(Gray code)让相邻十进制数只差一位,能削弱交叠时基因型小幅变动引发表现型跳变的问题,对价格阈值类参数更友好。 开 MT5 新建 EA,把手里的均线周期、止损点数先写成二进制染色体,就能直接套后面的选择与直线交叠逻辑做验证。外汇与贵金属杠杆高,回测优解实盘可能失效,任何寻优结果都只是概率倾向。
◍ 把遗传算法当可拆解的优化工具箱
遗传算法在 MQL5 里不只是黑盒优化器,它底层那套选择、交叉、变异的例程,完全可以抽出来当通用模块用。 本文要做的,是给你一套能在自定义方案里拼装优化逻辑的原语,而不只是跑通一个 GA 模板。 重点在于:同一套框架能验证任意优化算法的问题结构——比如你怀疑自己的网格搜索在参数空间里漏了局部极值,就能用这里的模块做对照实验。外汇与贵金属品种点差跳变频繁,这类实验请在策略测试器用真实 tick 回测,结果仅代表历史样本,实盘仍属高风险。
实数与二进制:优化特征的两种编码底色
在遗传类优化里,被调的参数叫“特征”,分表现型(肉眼看到的参数值)和基因型(算法内部存的形态)。多数算法里两者一致,直接用实数表达;染色体就是一组基因,也就是一组待优参数。 实数带小数点,比如 3.14、0.5,写策略和日常读数都顺手。MQL5 的 double 类型按 IEEE 754 编码,总共只有 16 个有效数位——像 9999999999999999.0、0.9999999999999999 这种,前后加起来顶多 16 个 9,超了就丢精度。这直接关系到EA里参数边界该怎么设。 二进制只用 0 和 1,每位叫一个比特。数字 5 写成 101。它的好处是把多个特征拼进同一个多维空间,突变、交叉这类按位操作直接挪比特就行,比实数省事。 缺点也实在:二进制最终还得转回实数交给优化目标算收益,转换和存比特数组都额外耗时间。外汇和贵金属参数优化本就高波动高风险,编码方式选错可能让搜索卡在局部解。 实数灵活控连续值,二进制擅整体拼空间和位运算。除了遗传算法,不少优化器会混用两者,各取所需。
「用格雷码抹平优化搜索里的盲点」
普通二进制有个恼人的特性:相邻十进制数可能触发多位翻转。7(0111)跳到 8(1000)时四个比特全变,导致搜索空间概率不均,出现“增加概率带”和“盲点”,浮点计算里更麻烦——小数点后微小变动可能让二进制表示大变样。 格雷码(反射二进制码)靠“单位距离属性”解决这事:相邻数只差一个比特位,序列过渡平滑,能回避不少精度丢失问题。 以优化参数边界 最小值=-156.675、最大值=456.6789 为例,距离=613.3539。把负数区间平移成 0~613.3539 的正数,整数和小数分别转格雷码:613(十)=1101010111(格),3539(十)=101100111010(格),用“:”拼成字符串存下,逆向也能还原。 这样实数整数部可塞 16 位、小数部也能到 16 位;ulong 长度够的话总有效位冲到 32 位以上也没压力。小数保 16 位时,最大小数 9999999999999999 的格雷码长达 54 位,613.9999999999999999 就按“整数:小数”格式拼进去。 下面四个函数管编解码。DecimalToGray 用 decimalNumber ^ (decimalNumber>>1) 算 grayCode 再交 IntegerToBinary;IntegerToBinary 循环取余填 char 数组后翻转;GrayToDecimal 借 BinaryToInteger 还原二进制再右移异或;BinaryToInteger 按 startInd~endInd 从字符串抽段转十进制。带起止索引的好处是:一条染色体存全部参数时,能只抠出某段还原,不用整条重算。
class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//Converting a decimal number to a Gray code class="type">void DecimalToGray(class="type">class="kw">ulong decimalNumber, class="type">char &array []) { class="type">class="kw">ulong grayCode = decimalNumber ^(decimalNumber >> class="num">1); IntegerToBinary(grayCode, array); } class=class="str">"cmt">//Converting a decimal number to a binary number class="type">void IntegerToBinary(class="type">class="kw">ulong number, class="type">char &array []) { ArrayResize(array, class="num">0); class="type">class="kw">ulong temp; class="type">int cnt = class="num">0; class="kw">while (number > class="num">0) { ArrayResize(array, cnt + class="num">1); temp = number % class="num">2; array [cnt] = (class="type">char)temp; number = number / class="num">2; cnt++; } ArrayReverse(array, class="num">0, WHOLE_ARRAY); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//Converting from Gray&class="macro">#x27;s code to a decimal number class="type">class="kw">ulong GrayToDecimal(const class="type">char &grayCode [], class="type">int startInd, class="type">int endInd) { class="type">class="kw">ulong grayCodeS = BinaryToInteger(grayCode, startInd, endInd); class="type">class="kw">ulong result = grayCodeS; class="kw">while ((grayCodeS >>= class="num">1) > class="num">0) { result ^= grayCodeS; } class="kw">return result; } class=class="str">"cmt">//Converting a binary class="type">class="kw">string to a decimal number