种群优化算法:改变概率分布的形状和位移,并基于智能头足类生物(SC)进行测试·进阶篇
📘

种群优化算法:改变概率分布的形状和位移,并基于智能头足类生物(SC)进行测试·进阶篇

第 2/2 篇

◍ 用成交量密度给画布点染色

这段片段把每个坐标点画成两圈圆:半径 2 与半径 3 各描一次,颜色由 CountR[i] 在 minCNT~maxCNT 区间映射到 0~255 的灰度值决定。 DoubleToColor 把归一化后的成交量计数转成颜色,COLOR2RGB 再喂给 Canvas.Circle,使密集区视觉上更亮。 循环结束后必须调 Canvas.Update() 才会把缓冲刷到图表;漏掉这行画面会静止。 在 MT5 里把这段接在你自己的计数数组后,改 2 和 3 两个半径值能直接看到点阵疏密变化,外汇与贵金属波动大,画布重绘频率高时需注意终端负载风险。

MQL5 / C++
    clrF = DoubleToColor(CountR [i], minCNT, maxCNT, class="num">0, class="num">255);
    Canvas.Circle(x, y, class="num">2, COLOR2RGB(clrF));
    Canvas.Circle(x, y, class="num">3, COLOR2RGB(clrF));
  }
  Canvas.Update();
}

三种随机分布的落地差异与边界坑

做 EA 参数扰动或样本生成时,均匀分布是最省事的:区间内每个值出现概率相等,实现上只要先掷一个 [0,1] 的随机数决定落在 In 左边还是右边,就不会出现越界伪影。它适合「无偏好扫区间」的场景,比如初始种群均匀撒网。 正态分布用 Box-Muller 变换生成,z0 = sqrt(-2*ln(u1)) * cos(2*pi*u2) 得到标准正态。麻烦在截断:若硬把 ±3σ 之外的数推回边界,边缘掉落频率会反常高于均值(图例2现象),破坏钟形。原文给出的 GaussDistribution 用了一个 8.583864105157389 的最大 σ 上限,并把越界概率向内「扩散」到曲线对应段,实测能保形。 幂律和 Levy 都有重尾,纯理论尾部无限长,不能直接限边界。幂律用 rnd 的 power 次幂近似,power<1 值堆近零像抛物线,power=1 退化为均匀,power>1 拖长尾像帕累托;Levy 则取 [1,20] 均匀数做负幂缩放,step 偶尔很长,探索参数空间比固定步长随机游走更快。外汇/贵金属优化里用这类分布做变异,属于高风险实验,结果仅代表概率倾向。 下面这段是三种分布里均匀部分的 MQL5 原生实现,以及高斯函数的前半段,逐行看逻辑比读文字快。

MQL5 / C++
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double UniformDistribution(const class="type">class="kw">double In, const class="type">class="kw">double outMin, const class="type">class="kw">double outMax)
{
  class="type">class="kw">double rnd = RNDfromCI(class="num">0.0, class="num">1.0);
  if (rnd >= class="num">0.5) class="kw">return RNDfromCI(In, outMax);
  else            class="kw">return RNDfromCI(outMin, In);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_SC::GaussDistribution(const class="type">class="kw">double In, const class="type">class="kw">double outMin, const class="type">class="kw">double outMax, const class="type">class="kw">double sigma)
{
  class="type">class="kw">double logN = class="num">0.0;
  class="type">class="kw">double u1   = RNDfromCI(class="num">0.0, class="num">1.0);
  class="type">class="kw">double u2   = RNDfromCI(class="num">0.0, class="num">1.0);
  logN = u1 <= class="num">0.0 ? class="num">0.000000000000001 : u1;

「截断正态与重尾分布的采样实现」

这段实现里,正态分布分支先做了 sigma 的上限钳制:当 sigma 超过 8.583864105157389 时强制压回该值,避免极端波动率把采样推太远。随后用黄色高亮的那段把超出 ±sigmaN 的 z0 重新丢回对应边界的均匀区间 RNDfromCI,等于给正态尾巴做了硬截断。 PowerDistribution 走的是幂律折叠:先取 [-1,1] 均匀随机,对其绝对值做 pow(rnd, power),power 越大样本越往 0 聚拢,再按正负映射进 [outMin, outMax]。 LeviDistribution 则用 r2 在 [1,20] 取均匀、对其取 -power 次幂得到重尾样本,min = 20^(-power) 作下界缩放;r1 过 0.5 概率向正侧映射,否则向负侧。外汇与贵金属行情用这类重尾采样做 Monte Carlo 压力测试时,需注意杠杆下的高风险,分布仅描述可能情形而非确定性路径。

MQL5 / C++
  class="type">class="kw">double z0 = sqrt(-class="num">2 * log(logN)) * cos(class="num">2 * M_PI * u2);
  class="type">class="kw">double sigmaN = sigma > class="num">8.583864105157389 ? class="num">8.583864105157389 : sigma;
  if (z0 >=  sigmaN) z0 = RNDfromCI(class="num">0.0,    sigmaN);
  if (z0 <= -sigmaN) z0 = RNDfromCI(-sigmaN, class="num">0.0);
  if (z0 >= class="num">0.0) z0 =  Scale(z0,         class="num">0.0, sigmaN, class="num">0.0, outMax - In, false);
  else            z0 = -Scale(fabs(z0), class="num">0.0, sigmaN, class="num">0.0, In - outMin, false);
  
  class="kw">return In + z0;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_SC::PowerDistribution(const class="type">class="kw">double In, const class="type">class="kw">double outMin, const class="type">class="kw">double outMax, const class="type">class="kw">double power)
{
  class="type">class="kw">double rnd = RNDfromCI(-class="num">1.0, class="num">1.0);
  class="type">class="kw">double r   = pow(fabs(rnd), power);
  if (rnd >= class="num">0.0) class="kw">return In + Scale(r, class="num">0.0, class="num">1.0, class="num">0.0, outMax - In, false);
  else            class="kw">return In - Scale(r, class="num">0.0, class="num">1.0, class="num">0.0, In - outMin, false);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">class="kw">double C_AO_SC::LeviDistribution(const class="type">class="kw">double In, const class="type">class="kw">double outMin, const class="type">class="kw">double outMax, const class="type">class="kw">double power)
{
  class="type">class="kw">double min = pow(class="num">20.0, -power);
  class="type">class="kw">double r1 = RNDfromCI(class="num">0.0, class="num">1.0);
  class="type">class="kw">double r2 = RNDfromCI(class="num">1.0, class="num">20.0);
  
  class="type">class="kw">double y = pow(r2, -power);
  y = Scale(y, min, class="num">1.0, class="num">0.0, class="num">1.0, false);
  if (r1 >= class="num">0.5) class="kw">return In + Scale(y, class="num">0.0, class="num">1.0, class="num">0.0, outMax - In, false);
  else           class="kw">return In - Scale(y, class="num">0.0, class="num">1.0, class="num">0.0, I - outMin, false);
}

◍ 用头足类隐喻写分布采样循环

把优化个体想象成一只智能头足类:头停在当前最优解 cB 上,触手按四种分布律向四周探食。代码里没有海洋生物,只有两层循环和一次分布切换。 外层循环跑满 popSize 个个体,内层循环逐个坐标 c 生成新位置。当前最优坐标从 cB[c] 取为 in,边界由 rangeMin[c]、rangeMax[c] 限定;distr 决定这一轮用均匀、高斯、幂律还是莱维分布来扰动 in。 四种分布函数都接收 in 与边界,部分还吃 powers 参数控制离散度;算出的 X 再交给 SeInDiSp 按 rangeStep 做栅格化,写回 a[i].c[c]。这样每只“触手”探到的点都被钳进可行域。 原作者在实测中发现 Rastrigin 函数作为测试面有隐藏缺陷,转而用 Peaks 函数替代,理由留到后续文章。外汇与贵金属参数优化属高风险实验,回测优解实盘可能失效,须以 MT5 自建样本验证。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//----------------------------------------------------------------------------</span>
&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; popSize; i++)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> c = <span class="number">class="num">0</span>; c &lt; coords; c++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> X&nbsp;&nbsp; = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> <span class="keyword">in</span>&nbsp;&nbsp;= cB [c];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> min = rangeMin [c];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> max = rangeMax [c];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">switch</span> (distr)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> uniformDistr: X = UniformDistribution(<span class="keyword">in</span>, min, max);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> gaussDistr:&nbsp;&nbsp; X = GaussDistribution&nbsp;&nbsp; (<span class="keyword">in</span>, min, max, powers); <span class="keyword">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> powerDistr:&nbsp;&nbsp; X = PowerDistribution&nbsp;&nbsp; (<span class="keyword">in</span>, min, max, powers); <span class="keyword">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> leviDistr:&nbsp;&nbsp;&nbsp;&nbsp;X = LeviDistribution&nbsp;&nbsp;&nbsp;&nbsp;(<span class="keyword">in</span>, min, max, powers); <span class="keyword">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a [i].c [c] = SeInDiSp&nbsp;&nbsp;(X, rangeMin [c], rangeMax [c], rangeStep [c]);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}

一点提醒

把概率分布和搜索策略拆开看都会失灵。作者 2011–2012 年从均匀分布转向非均匀分布,起因是想多挖已知坐标邻域、少浪费远端未知区;后来发现边界处新值人工堆积的伪影,根源正是没做分布偏移。 真正能落地的是两者协同:用分布偏移把概率捏成你需要的形状,再配搜索策略去探解空间。MT5 标准 GA 染色体长度受限,步长和参数数成反比,复杂变态优化还是得靠这类自定义工具。 外汇与贵金属优化回测高风险,过拟合和样本外崩坏是常态。开 MT5 把作者 github 的 Population-optimization-algorithms-MQL5 拉下来,先跑通分布偏移再谈调参。

常见问题

按固定价格桶统计成交量占比,用占比映射颜色透明度,占比低于阈值的桶直接降权不染色,避免噪声点抢视觉重心。
截断正态会丢失尾部极端行情概率,重尾分布则容易过度放大跳空风险;建议先用历史分位数标定截断边界再落地。
小布可读取你标好的成交量密度画布,自动跑多种分布采样并对比命中率,把重复劳动交给它,你专注看分布边界是否合理。
把群体探索与 exploitation 拆成触手搜索和捕食两步,循环结构更直观,便于调参时定位是探索不足还是收敛过快。
贵金属受事件跳空更频繁,重尾权重若设太高会在低流动性时段频繁误触发,务必加波动率过滤再上实盘。