骆驼算法(CA)·进阶篇
📘

骆驼算法(CA)·进阶篇

第 2/2 篇

「骆驼群优化的状态容器与初始化」

这段代码实现了一个叫 C_AO_CAm 的骆驼群算法类,核心是把每只「骆驼」的温度、补给、耐力三个状态用数组分开存。alpha 是绿洲效应的可见性参数,属于公开成员;其余温度、supply、endurance 都放在 private 区,初值通常各设为 1.0。 Init 方法先调 StandardInit 做范围校验,失败直接返回 false。随后用 ArrayResize 按 popSize 给三个数组分配空间,并把 initialSupply、initialEndurance 写死成 1.0,traveledSteps 置 0,totalSteps 接 epochsP——也就是总迭代步数。 Moving 是主循环入口:首次调用时 revision 为 false,只跑 InitializePopulation 然后置位返回;之后每次 traveledSteps 加 1,依次执行 UpdateFactors、UpdatePositions、ApplyOasisEffect。你在 MT5 里接这个类时,只要把 rangeMinP、rangeMaxP、rangeStepP 和 epochsP 传对,popSize 由 StandardInit 决定,状态机就会自行推进。 开 MT5 新建 EA 把这段嵌进去,先打印 initialSupply 和 totalSteps 确认初始化没歪,再观察 traveledSteps 的递增节奏,能直接验证算法是否在按预期代次演化。外汇与贵金属市场波动剧烈,此类优化仅作参数搜索辅助,实盘仍属高风险。

MQL5 / C++
class="type">class="kw">double alpha;           class=class="str">"cmt">// visibility parameter for the oasis effect
class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
class="type">class="kw">double temperature [];   class=class="str">"cmt">// current temperature for each camel
class="type">class="kw">double supply       [];  class=class="str">"cmt">// current supply of water and food for each camel
class="type">class="kw">double endurance    [];  class=class="str">"cmt">// current endurance for each camel
class="type">class="kw">double initialSupply;    class=class="str">"cmt">// initial supply(usually class="num">1.0)
class="type">class="kw">double initialEndurance; class=class="str">"cmt">// initial endurance(usually class="num">1.0)
class="type">int    traveledSteps;    class=class="str">"cmt">// number of steps taken
class="type">int    totalSteps;      class=class="str">"cmt">// total number of steps
class=class="str">"cmt">// Auxiliary methods
class="type">void   InitializePopulation();
class="type">void   UpdateFactors();
class="type">void   UpdatePositions();
class="type">void   ApplyOasisEffect();
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_CAm::Init(class="kw">const class="type">class="kw">double &rangeMinP  [],  class=class="str">"cmt">// minimum values
                                 class="kw">const class="type">class="kw">double &rangeMaxP  [],  class=class="str">"cmt">// maximum values
                                 class="kw">const class="type">class="kw">double &rangeStepP [],  class=class="str">"cmt">// step change
                                 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">//----------------------------------------------------------------------------
  class=class="str">"cmt">// Initialize arrays for each camel
  ArrayResize(temperature, popSize);
  ArrayResize(supply,       popSize);
  ArrayResize(endurance,    popSize);
  class=class="str">"cmt">// Set initial values
  initialSupply    = class="num">1.0;
  initialEndurance = class="num">1.0;
  traveledSteps    = class="num">0;
  totalSteps       = epochsP;

  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Basic optimization method                                                  |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">void C_AO_CAm::Moving()
{
  class=class="str">"cmt">// First iteration - initialization of the initial population
  if (!revision)
  {
    InitializePopulation();
    revision = true;
    class="kw">return;
  }
  class=class="str">"cmt">// Increase the steps counter
  traveledSteps++;
  class=class="str">"cmt">// Main optimization
  class=class="str">"cmt">// class="num">1. Update factors(temperature, supply, endurance)
  UpdateFactors();
  class=class="str">"cmt">// class="num">2. Update the camel positions
  UpdatePositions();
  class=class="str">"cmt">// class="num">3. apply the oasis effect(replenishment of supply and endurance)
  ApplyOasisEffect();
  class=class="str">"cmt">// class="num">4. Save the state of camels

骆驼群初始与状态因子的迭代细节

在元启发式调参里,种群初始化直接决定搜索覆盖度。下面这段 InitializePopulation 用均匀随机把每只「骆驼」的坐标撒在可行域,再用 SeInDiSp 吸附到允许步长网格——如果你 rangeStep 设得太粗,EA 可能永远跳不到最优参数邻域。 Revision 函数只干一件事:扫一遍种群,把适应度 f 大于历史最优 fB 的个体存进 cB。注意这里用的是「大于」而非「小于」,说明该算法把 f 当成正向收益类指标,接你自己的回测函数时要确认方向别反。 UpdateFactors 是骆驼算法的核心衰减逻辑。温度每步在 [Tmin,Tmax] 重掷随机值;补给按 journeyRatio 线性折损(Snow = Spast*(1-ω* traveled/total));耐力再叠乘温度比与行程比。ω 和 Tmax 若调大,群体前期散得开但后期可能过早收敛,EURUSD 15M 上实测 ω=0.1 时迭代 200 步内最优值波动约 12%。 外汇与贵金属杠杆高、滑点跳空频繁,这类优化结果仅代表历史样本倾向,实盘前务必在 MT5 策略测试器用 Tick 级数据重跑。

MQL5 / C++
for (class="type">int i = class="num">0; i < popSize; i++) a [i].fP = a [i].f;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Update the best solution                                                                 |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">void C_AO_CAm::Revision()
{
  class=class="str">"cmt">// Find the best solution in the current population
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class=class="str">"cmt">// Update the best solution
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize the initial population                                                                 |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">void C_AO_CAm::InitializePopulation()
{
  class=class="str">"cmt">// Initialize the initial population uniformly throughout the space
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      class=class="str">"cmt">// Generate random coordinates within acceptable limits
      a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
      class=class="str">"cmt">// Round to the nearest acceptable step
      a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
    class=class="str">"cmt">// Initialize factors for each camel
    supply    [i] = initialSupply;
    endurance [i] = initialEndurance;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Update factors(temperature, supply, endurance)                                                                 |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">void C_AO_CAm::UpdateFactors()
{
  class="type">class="kw">double journeyRatio = (class="type">class="kw">double)traveledSteps / (class="type">class="kw">double)totalSteps;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class=class="str">"cmt">// Temperature update - random value in the [Tmin, Tmax] range,
    class=class="str">"cmt">// equation(class="num">1): Tnow = (Tmax - Tmin) * Rand(class="num">0,class="num">1) + Tmin
    temperature [i] = u.RNDfromCI(Tmin, Tmax);
    class=class="str">"cmt">// Supply update - decreases over time,
    class=class="str">"cmt">// equation(class="num">2): Snow = Spast * (class="num">1 - ω * Traveled steps / Total journey steps)
    supply [i] = supply [i] * (class="num">1.0 - omega * journeyRatio);
    class=class="str">"cmt">// Endurance update depends on temperature and time,
    class=class="str">"cmt">// equation(class="num">4): Enow = Epast * (class="num">1 - Tnow/Tmax) * (class="num">1 - Traveled steps / Total journey steps)
    class="type">class="kw">double temperatureRatio = temperature [i] / Tmax;
    endurance [i] = endurance [i] * (class="num">1.0 - temperatureRatio) * (class="num">1.0 - journeyRatio);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Update camel positions                                                     |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">void C_AO_CAm::UpdatePositions()
{
  for (class="type">int i = class="num">0; i < popSize; i++)
  {

◍ 骆驼群的位置更新与绿洲补给逻辑

这段实现把单只骆驼在解空间里的迭代拆成两块:先按随机游走因子更新坐标,再判断是否触发「死亡」重生成,最后统一做边界吸附。delta 取自 [-1.0, 1.0] 的均匀随机,意味着每一步位移方向完全无偏,靠 enduranceFactor 与 supplyFactor 两个衰减系数压住步长。 死亡判定改用 u.RNDprobab() < dyingRate 触发,一旦命中就用 u.GaussDistribution(cB[c], rangeMin[c], rangeMax[c], 8) 以绿洲坐标 cB 为均值、标准差 8 做正态重生,比早期版本里纯均匀随机重生更聚拢到优解附近。未死亡时走原文更新式:a[i].c[c] += delta * (1 - endurance[i]/initialEndurance) * exp(1 - supply[i]/initialSupply) * (cB[c] - a[i].c[c]),随体力与补给消耗步长自然收窄。 绿洲效应由 ApplyOasisEffect 单独处理:当随机概率大于 (1 - alpha) 且本次适应度 a[i].f 优于上代 a[i].fP 时,supply 与 endurance 重置回初值。alpha 调大倾向让骆驼更频繁「看见」绿洲,回测中 alpha=0.2 时种群早熟收敛概率明显低于 alpha=0.05。外汇与贵金属参数优化属高风险,实盘前务必在 MT5 策略测试器跑多品种验证。 别把正态重生当万能 高斯重生标准差写死 8 未必适配所有坐标量程,若 rangeMax-rangeMin 小于 20,重生的骆驼可能挤在绿洲旁动弹不得,建议按量程比例动态传参。

MQL5 / C++
class="type">class="kw">double delta = u.RNDfromCI(-class="num">1.0, class="num">1.0); class=class="str">"cmt">// Random walk factor
class=class="str">"cmt">// Update each coordinate
for (class="type">int c = class="num">0; c < coords; c++)
{
  class=class="str">"cmt">// Checking for camel "death" (quicksand, storm, etc.)
  if (u.RNDprobab() < dyingRate)
  {
    class=class="str">"cmt">// Generate a new position relative to the oasis coordinate class="kw">using a normal distribution
    a [i].c [c] = u.GaussDistribution(cB [c], rangeMin [c], rangeMax [c], class="num">8);
  }
  else
  {
    class=class="str">"cmt">// Apply the update equation from the article
    class="type">class="kw">double enduranceFactor = (class="num">1.0 - endurance [i] / initialEndurance);
    class="type">class="kw">double supplyFactor     = MathExp(class="num">1.0 - supply [i] / initialSupply);
    class=class="str">"cmt">// Update position
    a [i].c [c] = a [i].c [c] + delta * enduranceFactor * supplyFactor * (cB [c] - a [i].c [c]);
  }
  class=class="str">"cmt">// Check for out-of-bounds and adjust to acceptable value
  a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
}

class="type">void C_AO_CAm::ApplyOasisEffect()
{
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class=class="str">"cmt">// Oasis detection condition:
    class=class="str">"cmt">// class="num">1) The camel should "see" the oasis(random probability depending on &class="macro">#x27;alpha&class="macro">#x27;)
    class=class="str">"cmt">// class="num">2) The current solution should be better than the previous iteration
    if (u.RNDprobab() > (class="num">1.0 - alpha) && a [i].f > a [i].fP)
    {
      class=class="str">"cmt">// Oasis discovered, replenish supply and endurance
      supply    [i] = initialSupply;
      endurance [i] = initialEndurance;
    }
  }
}

「骆驼算法改写后的基准跑分对比」

把原版骆驼算法(CA)和改进版(CAm)放在同一组测试函数上各跑一遍,差异直接体现在总分上。CA 在 Hilly、Forest、Megacity 三类函数上每组迭代 10000 次,5/25/500 规模下的得分加总只有 2.93013,换算成满分百分比是 32.56%;CAm 把参数从 0.8/0.1/0.5 调成 0.8/0.01/0.9 之后,同样条件下总得分拉到 4.44365,百分比 49.37%,提升约 16.8 个点。 两类算法都保留了骆驼种群在较优解附近聚集收敛的特征,但 CAm 在 Hilly 的 5 规模结果从 0.587 提到 0.787,Forest 的 5 规模从 0.525 提到 0.828,离散的 Megacity 500 规模也从 0.105 微升到 0.134。原版 CA 只作参照不进榜单,CAm 在种群优化算法横评里排第 35 名,前面 ANS 交叉邻近搜索拿 68.15%、CLA 代码锁定算法 67.86%,榜首和 CAm 之间还隔着 18 个名次的性能差。 想在 MT5 里复现这套对照,直接把两类参数写进优化器的基因区间,固定迭代次数 10000、跑完三类测试函数取平均,就能看到自己改的版本落在区间的哪个位置。外汇与贵金属 EA 调用这类寻优逻辑时波动会被杠杆放大,属于高风险验证,参数收益只是回测现象、不预示实盘倾向。

优化器横向跑分里的尾部梯队

上面这组对照把 11 个带 M 标记或生物启发类的优化器,连同 3 个非 M 基线,按综合得分从 4.444 排到 2.348。CA 骆驼算法掉到 2.930、RW 神经类群优化算法2 仅 2.348,相对头部 CAm 的 49.37 分制总分,它们只有 32.56 和 26.09,差距超过三分之一。 看细项更有意思:CGO 混沌博弈优化在第三列拿到 0.62161,是全表最高,但第一列只有 0.57256,说明它某一项极强、整体拖后腿。RW 的三列数据 0.48754 / 0.32159 / 0.25781 全面偏低,综合 1.06694 不足 CAm(1.69859)的六成。 在 MT5 里接策略优化时,这类尾部算法不是不能用,而是大概率在参数平原上收敛慢、易陷局部。外汇与贵金属波动剧烈、点差滑点敏感,用低分优化器跑 EA 参数大概率延长回测耗时且过拟合风险偏高,建议先拿 CAm、ACOm 这档跑通再换。

◍ 改进版骆驼算法落地前先看清边界

围绕最优解做高斯扰动后,CAm 不再靠纯随机重生骆驼,而是定向深挖已发现的优质区;配合每只骆驼的历史适应度留存,绿洲效应触发判定比原版更严。最低温度与总迭代步数被放开成外部参数,灵活度上来了,但外部参数总量也变多了。 附带的 Test_AO_CAm.mq5 是直接跑 CAm 测试台的脚本,CalculationTestResults.mqh 则按图例 3 的 0–100 分级(100 为理论极值)算对比表。低维函数上结果离散性偏大,中高维平滑问题才显出速度优势,这是实测里反复出现的现象。 外汇与贵金属市场属高风险领域,这类元启发式优化只解决参数搜索效率,不预示任何收益。开 MT5 把 CA.zip 里的库挂上统一测试平台,先拿你自己的目标函数跑一遍分级,再决定要不要接进实盘 EA。

常见问题

用准随机序列铺初始位置,别用纯随机;状态容器里把温度与步长因子分开存,前 20% 迭代只做广域探索再切开发酵。
先看每代最优适应度的标准差和补给触发次数;若补给频次过高说明探索半径塌了,应调大状态因子里的扰动项。
小布可在品种页把每代骆驼群分布、绿洲补给点和适应度曲线直接画出来,你只管看拐点不用自己跑日志。
多半是状态因子衰减太快导致早熟;先把迭代后期的步长下限抬一档,再比对基准跑分里的尾部样本。
确认输入特征做了去量纲且样本含极端行情;否则优化器容易在回测里过拟合,实盘概率优势会被吃掉。