种群优化算法:二进制遗传算法(BGA)。第 II 部分·进阶篇
(2/3)· 搞懂 BGA 的二进制表达与算子机制,才不会把 EA 参数优化做成瞎蒙
「遗传算法Agent与种群的结构骨架」
这段结构定义把遗传算法的核心数据容器一次性铺开:单个解向量由 S_Agent 承载,其内部用 chromosome 字符数组拼接各维基因的二进制位,coords 维参数各自带 min/max 边界与小数位精度。 Init 里先 ArrayResize(c, coords) 给坐标数组定容,再把 f 初始化为 -DBL_MAX,意味着适应度在首轮评估前处于“未命中”状态;染色体预留 1000 增量空间,循环里每维基因生成后通过 ArrayCopy 追加进 chromosome,pos 偏移由 gene.length 累加控制。 S_Roulette 只存 start/end 双精度,是典型的轮盘赌选择区间结构,不夹带多余字段。C_AO_BGA 作为主类暴露 cB/fB(最优坐标与适应度)、a[] 种群、以及 rangeMin/rangeMax/rangeStep 三组搜索边界,Init 入参已出现 coordsP、popSizeP、parentPopSizeP、crossoverProbabP——种群规模与交叉概率在构造时即固化。 在 MT5 里把这段 struct/class 原样贴入 EA 头文件,改 doubleDigitsInChromo 参数即可验证染色体长度对内存占用的线性影响;外汇与贵金属参数寻优属高风险实验,回测过拟合概率偏高,须用样本外数据复核。
class="kw">struct S_Agent { class="type">void Init(const class="type">int coords, const class="type">class="kw">double &min [], const class="type">class="kw">double &max [], class="type">int doubleDigitsInChromo) { ArrayResize(c, coords); f = -DBL_MAX; ArrayResize(genes, coords); ArrayResize(chromosome, class="num">0, class="num">1000); for(class="type">int i = class="num">0; i < coords; i++) { genes [i].Init(min [i], max [i], doubleDigitsInChromo); ArrayCopy(chromosome, genes [i].gene, ArraySize(chromosome), class="num">0, WHOLE_ARRAY); } calculated = class="kw">false; } class="type">void ExtractGenes() { class="type">uint pos = class="num">0; for (class="type">int i = class="num">0; i < ArraySize(genes); i++) { c [i] = genes [i].ToDouble(chromosome, pos); pos += genes [i].length; } } class="type">class="kw">double c []; class=class="str">"cmt">//coordinates class="type">class="kw">double f; class=class="str">"cmt">//fitness S_BinaryGene genes []; class="type">char chromosome []; class="type">bool calculated; }; class="kw">struct S_Roulette { class="type">class="kw">double start; class="type">class="kw">double end; }; class C_AO_BGA { class="kw">public: class="type">class="kw">double cB []; class=class="str">"cmt">//best coordinates class="kw">public: class="type">class="kw">double fB; class=class="str">"cmt">//FF of the best coordinates class="kw">public: S_Agent a []; class=class="str">"cmt">//agent class="kw">public: class="type">class="kw">double rangeMax []; class=class="str">"cmt">//maximum search range class="kw">public: class="type">class="kw">double rangeMin []; class=class="str">"cmt">//manimum search range class="kw">public: class="type">class="kw">double rangeStep []; class=class="str">"cmt">//step search class="kw">public: class="type">void Init(const class="type">int coordsP, class=class="str">"cmt">//coordinates number const class="type">int popSizeP, class=class="str">"cmt">//population size const class="type">int parentPopSizeP, class=class="str">"cmt">//parent population size const class="type">class="kw">double crossoverProbabP, class=class="str">"cmt">//crossover probability
◍ 遗传算法类的成员变量与接口拆解
在 MT5 里用遗传算法做参数寻优,第一步是把算法骨架落进一个 C++ 风格的类。下面这段声明定义了种群规模、交叉/变异/倒位概率等核心旋钮,以及染色体灰度码长度这类底层结构。 构造函数参数里 crossoverPointsP 控制单点还是多点交叉,mutationProbabP 与 inversionProbabP 是 [0,1] 区间的浮点,doubleDigitsInChromoP 决定基因十进制精度——比如设 4 就代表染色体解码后保留四位小数,直接影响手数或挂单价步长。 私有成员中 lengthChrome 按 Gray 码算字符穿长度,points 与 poRND 管理染色体断点索引,roulette 是实现轮盘赌选择的数组。开 MT5 新建 EA 时,把这些字段原样抄进头文件,就能在 Strategy Tester 里跑出可复现的种群进化;外汇与贵金属杠杆高,回测盈利不代表实盘胜率,参数过拟合概率偏大。 别把正态当圣经:mutationProbab 设 0.01 还是 0.1,种群多样性差异巨大,建议先用 0.05 跑 200 代看收敛曲线再调。
const class="type">int crossoverPointsP, class=class="str">"cmt">//crossover points const class="type">class="kw">double mutationProbabP, class=class="str">"cmt">//mutation probability const class="type">class="kw">double inversionProbabP, class=class="str">"cmt">//inversion probability const class="type">int doubleDigitsInChromoP);class=class="str">"cmt">//number of decimal places in the gene class="kw">public: class="type">void Moving(); class="kw">public: class="type">void Revision(); class=class="str">"cmt">//---------------------------------------------------------------------------- class="kw">private: class="type">int coords; class=class="str">"cmt">//coordinates number class="kw">private: class="type">int popSize; class=class="str">"cmt">//population size class="kw">private: class="type">int parentPopSize; class=class="str">"cmt">//parent population size class="kw">private: class="type">class="kw">double crossoverProbab; class=class="str">"cmt">//crossover probability class="kw">private: class="type">int crossoverPoints; class=class="str">"cmt">//crossover points class="kw">private: class="type">class="kw">double mutationProbab; class=class="str">"cmt">//mutation probability class="kw">private: class="type">class="kw">double inversionProbab; class=class="str">"cmt">//inversion probability class="kw">private: class="type">int doubleDigitsInChromo; class=class="str">"cmt">//number of decimal places in the gene class="kw">private: class="type">bool revision; class="kw">private: S_Agent parents []; class=class="str">"cmt">//parents class="kw">private: class="type">int ind []; class=class="str">"cmt">//temporary array for sorting the population class="kw">private: class="type">class="kw">double val []; class=class="str">"cmt">//temporary array for sorting the population class="kw">private: S_Agent pTemp []; class=class="str">"cmt">//temporary array for sorting the population class="kw">private: class="type">char tempChrome []; class=class="str">"cmt">//temporary chromosome for inversion surgery class="kw">private: class="type">uint lengthChrome; class=class="str">"cmt">//length of the chromosome(the length of the class="type">class="kw">string of characters according to the Gray code) class="kw">private: class="type">int pCount; class=class="str">"cmt">//indices of chromosome break points class="kw">private: class="type">uint poRND []; class=class="str">"cmt">//temporal indices of chromosome break points class="kw">private: class="type">uint points []; class=class="str">"cmt">//final indices of chromosome break points class="kw">private: S_Roulette roulette []; class=class="str">"cmt">//roulette class="kw">private: class="type">void PreCalcRoulette(); class="kw">private: class="type">int SpinRoulette();
遗传算法类的私有方法与初始化落点
C_AO_BGA 把几个底层操做封装成 private 方法:SeInDiSp 做区间离散化,RNDfromCI 在 [min,max] 内取均匀随机数,Sorting 对 Agent 指针数组按适应度排序,Scale 则是把输入区间线性映射到输出区间。这类接口不暴露给调用方,只服务于内部进化循环。 Init 是真正干活的地方。先用 MathSrand((int)GetMicrosecondCount()) 以微秒计数重置随机种子,避免每次回测初始种群雷同;随后把 fB 置为 -DBL_MAX、revision 置 false,并批量把入参写进成员变量。 几个防御式下限值得注意:crossoverPoints 和 pCount 若小于 1 会被强制拉回 1,否则单点交叉都跑不起来。数组尺寸按 parentPopSize+popSize 预留,roulette 只开 parentPopSize 长度,说明轮盘赌仅在父代规模上做选择。 直接把这段 Init 抄进 MT5 的 EA 头文件,改 coords 和 popSize 两个入参,就能在策略测试器里观察种群数组实际占用的内存峰值,外汇与贵金属品种下高 popSize 可能显著拖慢 tick 级回测。
class="kw">private: class="type">class="kw">double SeInDiSp(class="type">class="kw">double In, class="type">class="kw">double InMin, class="type">class="kw">double InMax, class="type">class="kw">double Step); class="kw">private: class="type">class="kw">double RNDfromCI(class="type">class="kw">double min, class="type">class="kw">double max); class="kw">private: class="type">void Sorting(S_Agent &p [], class="type">int size); class="kw">private: 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); }; class="type">void C_AO_BGA::Init(const class="type">int coordsP, class=class="str">"cmt">//coordinates number const class="type">int popSizeP, class=class="str">"cmt">//population size const class="type">int parentPopSizeP, class=class="str">"cmt">//parent population size const class="type">class="kw">double crossoverProbabP, class=class="str">"cmt">//crossover probability const class="type">int crossoverPointsP, class=class="str">"cmt">//crossover points const class="type">class="kw">double mutationProbabP, class=class="str">"cmt">//mutation probability const class="type">class="kw">double inversionProbabP, class=class="str">"cmt">//inversion probability const class="type">int doubleDigitsInChromoP) class=class="str">"cmt">//number of decimal places in the gene { MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator fB = -DBL_MAX; revision = class="kw">false; coords = coordsP; popSize = popSizeP; parentPopSize = parentPopSizeP; crossoverProbab = crossoverProbabP; crossoverPoints = crossoverPointsP; pCount = crossoverPointsP; mutationProbab = mutationProbabP; inversionProbab = inversionProbabP; doubleDigitsInChromo = doubleDigitsInChromoP; if (crossoverPoints < class="num">1) crossoverPoints = class="num">1; if (pCount < class="num">1) pCount = class="num">1; ArrayResize(poRND, pCount); ArrayResize(points, pCount + class="num">2); ArrayResize(ind, parentPopSize + popSize); ArrayResize(val, parentPopSize + popSize); ArrayResize(pTemp, parentPopSize + popSize); ArrayResize(a, popSize); ArrayResize(parents, parentPopSize + popSize); ArrayResize(roulette, parentPopSize); ArrayResize(rangeMax, coords); ArrayResize(rangeMin, coords);
「遗传算法的种群初始化与繁殖循环」
这段 C_AO_BGA::Moving 方法实现了基于遗传算法的参数寻优核心逻辑,第一次调用时 revision 为 false,会先铺满初始种群。 初始化阶段对每个个体调用 Init 并按 MathRand() 返回的 [0,32767] 区间随机数填染色体:大于 16384 置 1,否则置 0,相当于用 50% 概率做二进制基因播种。 随后 ExtractGenes 解码,再用 SeInDiSp 把基因映射到各维度的 rangeMin~rangeMax 实值空间,个体适应度 f 统一设为 -DBL_MAX 等待回测打分。 非首次进入时走繁殖分支:PreCalcRoulette 建轮盘、SpinRoulette 按适应度概率选父代,ArrayCopy 把父代染色体原样拷给子代,再掷 RNDfromCI(0,1) 决定是否以 crossoverProbab 概率交叉。 交叉时随机取 pCount 个断点(poRND),越界则钳到 lengthChrome-1,ArraySort 排序后做多点重组——在 MT5 里把 pCount 和 crossoverProbab 调大,种群可能更快跳出局部最优,但外汇与贵金属杠杆品种的高波动会让过拟合风险明显上升。
ArrayResize(rangeStep, coords); ArrayResize(cB, coords); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_BGA::Moving() { class=class="str">"cmt">//---------------------------------------------------------------------------- if (!revision) { for (class="type">int i = class="num">0; i < popSize; i++) { a [i].Init(coords, rangeMin, rangeMax, doubleDigitsInChromo); class="type">int r = class="num">0; for (class="type">int len = class="num">0; len < ArraySize(a [i].chromosome); len++) { r = MathRand(); class=class="str">"cmt">//[class="num">0,class="num">32767] if (r > class="num">16384) a [i].chromosome [len] = class="num">1; else a [i].chromosome [len] = class="num">0; } a [i].ExtractGenes(); for (class="type">int c = class="num">0; c < coords; c++) a [i].c [c] = SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); a [i].f = -DBL_MAX; a [i].calculated = true; } lengthChrome = ArraySize(a [class="num">0].chromosome); ArrayResize(tempChrome, lengthChrome); for (class="type">int i = class="num">0; i < parentPopSize + popSize; i++) { parents [i].Init(coords, rangeMin, rangeMax, doubleDigitsInChromo); parents [i].f = -DBL_MAX; } revision = true; class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">int pos = class="num">0; class="type">class="kw">double r = class="num">0; class="type">uint p1 = class="num">0; class="type">uint p2 = class="num">0; class="type">uint p3 = class="num">0; class="type">uint temp = class="num">0; for (class="type">int i = class="num">0; i < popSize; i++) { PreCalcRoulette(); class=class="str">"cmt">//selection, select and copy the parent to the child------------------------ pos = SpinRoulette(); ArrayCopy(a [i].chromosome, parents [pos].chromosome, class="num">0, class="num">0, WHOLE_ARRAY); class=class="str">"cmt">//crossover----------------------------------------------------------------- r = RNDfromCI(class="num">0.0, class="num">1.0); if (r < crossoverProbab) { class=class="str">"cmt">//choose a second parent to breed with------------------------------------ pos = SpinRoulette(); class=class="str">"cmt">//determination of chromosome break points-------------------------------- for (class="type">int p = class="num">0; p < pCount; p++) { poRND [p] = (class="type">int)RNDfromCI(class="num">0.0, lengthChrome); if (poRND [p] >= lengthChrome) poRND [p] = lengthChrome - class="num">1; } ArraySort(poRND);