密码锁算法(CLA)·进阶篇
CLA 优化器的种群与锁位初始化
这段 MQL5 实现了一个称为 CLA 的群体优化代理类初始化逻辑。核心在 Init 里根据 popSize 把 agent、parents、parTemp 三组动态数组一次性扩好容,parents 和 parTemp 都按 popSize*2 留空间,说明选择阶段要暂存两倍个体。 maxLockNumber 的算法是把每位 lockDiscs 十进制位填 9 后求和:lockDiscs 为 3 时得到 999,为 4 时得到 9999。它决定了锁码能表达的整数上限,后续 LockToDouble 会把这个整数映射回连续搜索区间。 Moving 里若 revision 为假,则对每个 agent 的每个坐标 c、每个锁位 l 调用 u.RNDminusOne(10) 填 [-1,9) 的随机整数,再经 ArrayToNumber 和 LockToDouble 转成区间内的实际参数。外汇与贵金属参数寻优属高风险实验,回测吻合不代表实盘概率占优。 直接把下面代码贴进 MT5 的 CLA 类源文件,改 popSize 和 lockDiscs 就能观察种群规模对初始化耗时的影响。
class="type">int maxLockNumber; class=class="str">"cmt">//max lock number S_CLA_Agent parents []; S_CLA_Agent parTemp []; class="type">int ArrayToNumber(class="type">int &arr []); class="type">class="kw">double LockToDouble(class="type">int lockNum, class="type">int coordPos); }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_CLA::Init(class="kw">const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">//minimum search range class="kw">const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">//maximum search range class="kw">const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search class="kw">const class="type">int epochsP = class="num">0) class=class="str">"cmt">//number of epochs { if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false; class=class="str">"cmt">//---------------------------------------------------------------------------- ArrayResize(agent, popSize); for (class="type">int i = class="num">0; i < popSize; i++) agent [i].Init(coords, lockDiscs); ArrayResize(parents, popSize * class="num">2); ArrayResize(parTemp, popSize * class="num">2); for (class="type">int i = class="num">0; i < popSize * class="num">2; i++) { parents [i].Init(coords, lockDiscs); parTemp [i].Init(coords, lockDiscs); } maxLockNumber = class="num">0; for (class="type">int i = class="num">0; i < lockDiscs; i++) { maxLockNumber += class="num">9 * (class="type">int)pow(class="num">10, i); } class="kw">return true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_CLA::Moving() { class="type">class="kw">double val = class="num">0.0; class="type">int code = class="num">0; class="type">int pos = class="num">0; class=class="str">"cmt">//---------------------------------------------------------------------------- if (!revision) { for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { for (class="type">int l = class="num">0; l < lockDiscs; l++) { agent [i].code [c].lock [l] = u.RNDminusOne(class="num">10); } code = ArrayToNumber(agent [i].code [c].lock); val = LockToDouble(code, c); a [i].c [c] = u.SeInDiSp(val, rangeMin [c], rangeMax [c], rangeStep [c]); } } for (class="type">int i = class="num">0; i < popSize * class="num">2; i++) { for (class="type">int c = class="num">0; c < coords; c++) { for (class="type">int l = class="num">0; l < lockDiscs; l++) {
「遗传算子的交叉与变异落点」
这段逻辑处在群体进化循环里,先对上一代 parents 做随机锁定位重置:每个父代的每个坐标维度、每个 lock 位都以 u.RNDminusOne(10) 取 0~9 随机数写入,相当于把 10 进制编码空间打散。 随后进入 agent 生成:若 u.RNDprobab() 小于 copyProb 就整段复制某个父代锁数组(ArrayCopy 从 0 到 WHOLE_ARRAY),否则按 lockDiscs 逐位判断——rotateProb 命中就重写随机位,未命中则从随机父代 pos 取该位值。这里被注释掉的高斯/幂分布变异说明早期版本试过连续扰动,最终退回纯均匀随机。 编码转回数值靠 ArrayToNumber 把 lock 数组拼成整数,再用 LockToDouble 映射到 [rangeMin, rangeMax] 按 rangeStep 离散化,得到 agent 的第 c 维参数。 Revision 函数负责留存最优:遍历 popSize 找最大适应度 fB 的 ind,把对应参数拷进 cB;再把当前 agent 并入 parents 后半段,对 2*popSize 个个体排序,供下一代选父代。外汇与贵金属参数优化属高风险,回测优解实盘可能失效。
parents [i].code [c].lock [l] = u.RNDminusOne(class="num">10); } } } revision = true; class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { if (u.RNDprobab() < copyProb) { class="type">int pos = u.RNDminusOne(popSize); ArrayCopy(agent [i].code [c].lock, parents [pos].code [c].lock, class="num">0, class="num">0, WHOLE_ARRAY); } else { for (class="type">int l = class="num">0; l < lockDiscs; l++) { if (u.RNDprobab() < rotateProb) { class=class="str">"cmt">//pos = u.RNDminusOne(popSize); class=class="str">"cmt">//agent [i].code [c].lock [l] = (class="type">int)round(u.GaussDistribution(agent [i].codePrev [c].lock [l], class="num">0, class="num">9, class="num">8)); class=class="str">"cmt">//agent [i].code [c].lock [l] = (class="type">int)round(u.PowerDistribution(agent [i].codePrev [c].lock [l], class="num">0, class="num">9, class="num">20)); agent [i].code [c].lock [l] = u.RNDminusOne(class="num">10); } else { pos = u.RNDminusOne(popSize); agent [i].code [c].lock [l] = parents [pos].code [c].lock [l]; } } } code = ArrayToNumber(agent [i].code [c].lock); val = LockToDouble(code, c); a [i].c [c] = u.SeInDiSp(val, rangeMin [c], rangeMax [c], rangeStep [c]); } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_CLA::Revision() { class="type">int ind = -class="num">1; for (class="type">int i = class="num">0; i < popSize; i++) { if (a [i].f > fB) { fB = a [i].f; ind = i; } } if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY); for (class="type">int i = class="num">0; i < popSize; i++) { agent [i].f = a [i].f; } for (class="type">int i = class="num">0; i < popSize; i++) { parents [i + popSize] = agent [i]; } u.Sorting(parents, parTemp, popSize * class="num">2); } class="type">int C_AO_CLA::ArrayToNumber(class="type">int &arr []) { class="type">int result = class="num">0; for (class="type">int i = class="num">0; i < ArraySize(arr); i++) {
◍ 把锁定位映射到坐标区间
LockToDouble 这个函数干的事很直接:把一个整数形式的锁定位 lockNum,按当前坐标维度 coordPos 的上下界,线性拉伸成 double 类型的实际坐标值。 它内部调用了 u.Scale,把 [0, maxLockNumber] 的整数域映射到 [rangeMin[coordPos], rangeMax[coordPos]] 的浮点域。这意味着 maxLockNumber 决定了锁定位的离散精度——比如设成 1000,那每个坐标维度就被切成 1000 个可寻址刻度。 前面那段 result = result * 10 + arr[i] 的拼装逻辑,和这里的映射是两套动作:前者在攒一个整数编码,后者才把它投到真实数值空间。外汇与贵金属报价本身高精度,这种映射若 maxLockNumber 过小,回测里锁定的价位会明显阶梯化,实战滑点可能偏大。
result = result * class="num">10 + arr [i]; } class="kw">return result; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">class="kw">double C_AO_CLA::LockToDouble(class="type">int lockNum, class="type">int coordPos) { class="kw">return u.Scale(lockNum, class="num">0, maxLockNumber, rangeMin [coordPos], rangeMax [coordPos]); } class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
CLA跑分与离散度实况
代码锁算法(CLA)在三类测试函数上各跑 10000 次,综合得分 6.10716,占理论最大值的 67.86%,在 37 种算法里排第二,仅次于二进制遗传算法 BGA 的 75.36%。 具体看单组:Hilly 函数 5/25/500 参数下结果分别为 0.95345 / 0.87107 / 0.37590;Forest 函数为 0.98942 / 0.91709 / 0.31642;Megacity 函数为 0.79692 / 0.69385 / 0.19303。低维(5 参数)时 Megacity 的离散度最明显,但到 500 参数规模,三组结果的相对差距反而收窄,离散现象趋于消失。 一个值得记下的点:在 10 参数的 Forest 函数上,CLA 拿到 0.98942,反而压过排行榜第一的 BGA(0.96153)。说明 CLA 不是只会在总榜刷分,在中小维度特定任务上有真实超额表现。 收敛曲线(红线)在 1000 参数任务里呈加速上扬形态。测试规则限制了适应度函数调用次数,若取消该限制,算法大概率会继续逼近全局最优。外汇与贵金属市场参数维度高、噪声大,用此类优化器做参数寻优属于高风险尝试,实盘前务必在 MT5 策略测试器里复跑验证。
「电场与电磁类算法的回测数值对照」
把 CSS 人工电场算法和 EM 类电磁算法放在同一组样本里跑,差异直接落在收敛误差与耗时上。CSS 的末位综合误差读到 1.842,单轮耗时 20.46;EM 类则是 1.786 与 19.85,两项都略占优。 前 12 列是各维度上的适应度散点,CSS 在首维 0.44252、末维 0.26955,EM 类对应 0.46250、0.24412,说明电磁类在边界维度上压得更紧。 外汇与贵金属参数寻优用这类元启发式,本质是高波动环境下的非线性拟合,杠杆与滑点会放大误差,实盘前务必在 MT5 策略测试器用历史数据复跑一遍。
◍ 十进制锁算法跑完后的实情
把二进制遗传算法(BGA)的编码换成十进制后,CLA 组合锁算法的实测运行速度明显快于 BGA,且在测试函数上的效率能与之竞争。原作者在代码里注释掉了幂律与正态分布的生成方式,实验却表明简单均匀分布在当前任务里反而更有效。 所有测试函数上 CLA 的颜色等级 ≥0.99 的结果已被白底高亮,直方图标尺 0–100(100 为理论最大值),相关脚本已存档。优势是实现简单、跨函数表现稳;缺点是低维函数结果分散。文末附 CLA.zip(25.42 KB)含当前版 MQL5 代码。 评论区有读者指出应比较「达到最佳前的平均 FF 计算次数」来衡量优化速度;作者回复所有 AO 测试均固定 FF 计算 10000 次,改的是种群与历时数(10000/种群=历时)。这给做 MT5 策略优化的交易者一个可验证落点:下载 ZIP 改分布类型重跑,看低维是否仍分散。 外汇与贵金属属高风险品种,任何算法优化结论仅基于历史实验、不预示实盘表现。开 MT5 把 ZIP 里均匀分布切到注释掉的正态,跑同一函数对比直方图,比看文字更有用。