密码锁算法(CLA)·进阶篇
📘

密码锁算法(CLA)·进阶篇

第 2/2 篇

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 就能观察种群规模对初始化耗时的影响。

MQL5 / C++
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 个个体排序,供下一代选父代。外汇与贵金属参数优化属高风险,回测优解实盘可能失效。

MQL5 / C++
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 过小,回测里锁定的价位会明显阶梯化,实战滑点可能偏大。

MQL5 / C++
  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 里均匀分布切到注释掉的正态,跑同一函数对比直方图,比看文字更有用。

常见问题

种群规模建议不低于80,锁位初始化用均匀随机覆盖全区间,可显著降低早熟概率;规模过小会更快陷进局部解。
交叉只对非锁定位执行,变异落点限制在邻域半径内;这样能在探索和解结构稳定之间保持平衡。
可以。小布能调用CLA优化流程,把锁定位映射到的坐标区间直接绘制在对应品种页面,省去手动抄参数的麻烦。
实况中CLA离散度约为电场类的0.6倍、电磁类的0.8倍;电场类收敛快但波动大,CLA更稳但耗时略长。
跑分优只代表样本内拟合,外汇贵金属高风险,需做样本外验证并控仓;收敛不等于未来概率占优。