圆搜索算法(CSA)(基础篇)
📘

圆搜索算法(CSA)(基础篇)

第 1/2 篇

圆搜索算法在 MT5 里的实现骨架

圆搜索算法(CSA)是一类以圆心加半径为搜索域的遍历优化思路,在 MetaTrader 5 上可直接用 MQL5 写成 EA 或脚本跑历史数据。它的核心不是穷举全空间,而是沿圆周采样候选点,再用目标函数筛选,适合参数敏感性平滑、局部极值不尖锐的场景。 社区里有人(Andrey Dik)在 2025 年 12 月 1 日贴过一版实现,帖子浏览量 478、零回复,说明这类底层算法帖在零售交易者里关注度偏低,但拿来做策略寻参有实用价值。 落地时建议先开 MT5 的「策略测试器」,把 CSA 当成外层寻参器包裹你的信号逻辑;外汇与贵金属杠杆高、滑点跳空频繁,圆周采样步长和半径若设太大,回测过拟合概率会明显上升,需用样本外数据做交叉验证。

◍ 圆切线怎么成了优化搜索的轨迹

CSA(Circle Search Algorithm)把每个搜索点当成圆周上的智能体,让它沿圆的切线滑动来探解空间。圆的恒定半径和连续导数给了它天然的数学平滑性,避免了不少元启发式算法里轨迹抖动的毛病。 算法把迭代拆成利用与探索两段:利用段方向性强,智能体往潜力区收拢;探索段则大胆跳进未扫过的区域。切换由当前迭代次数和一个叫 c 的特殊参数共同控制,c 的大小直接决定早期偏乱跑还是后期偏收敛。 每个智能体的行进轨迹由动态调整的 θ 角决定,种群里没有两条完全一样的路径。这套几何模型的好处是高维下仍跑得动,且逻辑能直接画出来讲明白——不像某些黑箱进化策略。 该算法由 Mohammad H. Kaiys、Hany M. Hasanien 等人在 2022 年提出。想在 MT5 上验证,可先拿它替换 EA 里的参数寻优循环,观察 c 取 0.2 与 0.8 时回测曲线收敛速度的差异。外汇与贵金属品种波动剧烈,任何优化结果仅代表历史样本倾向,实盘仍属高风险。

「圆搜索算法的代码骨架与参数演化」

CSA 把每个搜索智能体当作圆上的切点,圆心 Xc 代表当前最优解,切点沿切线向圆心收拢。原版用 w = w × rand - w 让角度参数混沌变化,最终版改成 w = π × (1 - epochNow/epochs),线性递减到 0,收敛轨迹更可控。位置更新也加了 rand 因子:Xi = Xc + rand × (Xc - Xi) × tan(θ),比原版纯确定性移动多了附加随机性,实测在多数测试函数上优于原版。 阶段切换看迭代进度:当 epochNow ≤ constC × epochs(constC 默认 0.8)时走探索,θ = w × random[0;1];否则进利用,θ = w × p。p 自身从 1.0 随迭代开方降到 0.1,利用阶段幅度自然收窄。 下面这段是 C_AO_CSA 类的构造与初始化主体,继承自 C_AO 基类,种群固定 50、探索阈值 0.8,初始 w/a 取 M_PI。代码逐行拆: 第1行注释分隔线,无实际逻辑。 class C_AO_CSA : public C_AO —— 声明类,公开继承优化器基类。 public: 访问控制,其下为对外方法。 C_AO_CSA() 构造函数:ao_name 设 "CSA",ao_desc 写算法全称,popSize=50 种群规模,constC=0.8 探索阶段常数,w/aParam 初值 M_PI,p=1.0,theta=0。 ArrayResize(params,2) 把参数数组扩到 2 个元素,分别存 popSize 和 constC 名称与值。 SetParams() 从 params 数组读回 popSize 和 constC 赋值给成员。 Init() 接收各维最小值、最大值、步长数组及 epochsP 迭代数,用于后续标准初始化与计数清零。 想在 MT5 里验证,直接把 popSize 改成 30 或把 constC 调到 0.6,看回测中早期探索广度是否变化——外汇与贵金属参数优化属高风险操作,结果仅代表历史样本倾向,不代表实盘表现。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class</span> C_AO_CSA : <span class="keyword">class="kw">public</span> C_AO
{
&nbsp;&nbsp;<span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--------------------------------------------------------------------</span>
&nbsp;&nbsp;C_AO_CSA()
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;ao_name = <span class="class="type">class="kw">string">"CSA"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;ao_desc = <span class="class="type">class="kw">string">"Circle Search Algorithm"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;ao_link = <span class="class="type">class="kw">string">"[MQL5官方文档]
&nbsp;&nbsp;&nbsp;&nbsp;popSize = <span class="number">class="num">50</span>;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// population size</span>
&nbsp;&nbsp;&nbsp;&nbsp;constC&nbsp;&nbsp;= <span class="number">class="num">0.8</span>;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// optimal value for the exploration phase</span>
&nbsp;&nbsp;&nbsp;&nbsp;w&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="macro">M_PI</span>;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// initial value w</span>
&nbsp;&nbsp;&nbsp;&nbsp;aParam&nbsp;&nbsp;= <span class="macro">M_PI</span>;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// initial value a</span>
&nbsp;&nbsp;&nbsp;&nbsp;p&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">1.0</span>;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// initial value p</span>
&nbsp;&nbsp;&nbsp;&nbsp;theta&nbsp;&nbsp; = <span class="number">class="num">0</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// initial value of the angle</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ArrayResize</span> (params, <span class="number">class="num">2</span>);
&nbsp;&nbsp;&nbsp;&nbsp;params [<span class="number">class="num">0</span>].name = <span class="class="type">class="kw">string">"popSize"</span>;&nbsp;&nbsp;&nbsp;&nbsp; params [<span class="number">class="num">0</span>].val = popSize;
&nbsp;&nbsp;&nbsp;&nbsp;params [<span class="number">class="num">1</span>].name = <span class="class="type">class="kw">string">"constC"</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;params [<span class="number">class="num">1</span>].val = constC;
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="keyword">class="type">void</span> SetParams()
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;popSize = (<span class="keyword">class="type">int</span>)params [<span class="number">class="num">0</span>].val;
&nbsp;&nbsp;&nbsp;&nbsp;constC&nbsp;&nbsp;= params&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<span class="number">class="num">1</span>].val;
&nbsp;&nbsp;}
&nbsp;&nbsp;<span class="keyword">class="type">bool</span> Init(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;rangeMinP&nbsp;&nbsp;[],&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// minimum values</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;rangeMaxP&nbsp;&nbsp;[],&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// maximum values</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;rangeStepP [],&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// step change</span>

CSA 优化器的初始化与移动逻辑

下面这段是 CSA(Cuckoo Search Algorithm)封装类的核心骨架,用在 MT5 自定义指标或 EA 里做参数寻优时,先看清它的状态机怎么转。 Init 函数接收参数下界、上界、步长数组以及 epochsP(迭代上限,默认 0 表示不限制)。它先调用 StandardInit 做边界与维度校验,再把 epochs 与 epochNow 置位——epochNow 从 0 起算,每跑一次 Moving 自增 1,这是判断搜索阶段切换的硬基准。 Moving 的第一轮(revision 为 false)会按均匀随机把整个种群撒进参数空间:a[i].c[j] 先用 RNDfromCI 抽连续值,再用 SeInDiSp 吸附到离散步点上,随后 revision 置 true 并直接返回。也就是说,第 0 代是纯随机播种,不参与角度寻优。 进入正式迭代后,w、aParam、p 分别由 CalculateW / CalculateA / CalculateP 线性更新,每个个体再算一次 theta 搜索角。外汇与贵金属市场波动率高、过拟合风险大,这类群智能寻参在实盘前必须用历史数据多窗口回测,结果仅代表历史样本下的概率倾向。 把下面代码直接贴进 MT5 的 include 头文件,就能在 OnInit 里传 range 数组起来跑;改 epochsP 的值可以卡死最大迭代轮数,避免优化器在窄区间内空转。

MQL5 / C++
const class="type">int    epochsP = class="num">0);  class=class="str">"cmt">// number of epochs
class="type">void Moving();
class="type">void Revision();
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double constC;       class=class="str">"cmt">// constant for determining the search phase [class="num">0,class="num">1]
class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
class="type">int epochs;          class=class="str">"cmt">// maximum number of iterations
class="type">int epochNow;        class=class="str">"cmt">// current iteration
class=class="str">"cmt">// Parameters for CSA
class="type">class="kw">double w;            class=class="str">"cmt">// parameter for calculating the angle
class="type">class="kw">double aParam;       class=class="str">"cmt">// parameter a from the equation(class="num">8)
class="type">class="kw">double p;            class=class="str">"cmt">// parameter p from the equation(class="num">9)
class="type">class="kw">double theta;        class=class="str">"cmt">// search angle
class="type">class="kw">double CalculateW();
class="type">class="kw">double CalculateA();
class="type">class="kw">double CalculateP();
class="type">class="kw">double CalculateTheta(class="type">class="kw">double currentW, class="type">class="kw">double currentP);
class="type">bool IsExplorationPhase();
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_CSA::Init(const class="type">class="kw">double &rangeMinP  [],
                     const class="type">class="kw">double &rangeMaxP  [],
                     const class="type">class="kw">double &rangeStepP [],
                     const class="type">int    epochsP = class="num">0)
{
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return false;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  epochs   = epochsP;
  epochNow = class="num">0;
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_CSA::Moving()
{
  epochNow++;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int j = class="num">0; j < coords; j++)
      {
        a [i].c [j] = u.RNDfromCI(rangeMin [j], rangeMax [j]);
        a [i].c [j] = u.SeInDiSp(a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
      }
    }
    revision = true;
    class="kw">return;
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  w      = CalculateW();    class=class="str">"cmt">// Update w linearly
  aParam = CalculateA();    class=class="str">"cmt">// Update a
  p      = CalculateP();    class=class="str">"cmt">// Update p
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    theta = CalculateTheta(w, p);
    for (class="type">int j = class="num">0; j < coords; j++)
    {

常见问题

核心是初始化一组绕中心点的候选解,按圆周切线方向迭代移动并更新最优值,可对照基础篇的代码骨架直接复制验证。
切线方向能在保持半径约束的同时覆盖邻域,比随机游走更省计算量,适合参数空间连续的场景。
小布盯盘的 AIGC 已内置该类形态诊断,打开对应品种页即可看到算法标注的潜在区间,不用自己写代码。
可能跳过局部精细结构导致收敛慢,建议先用小半径试跑再按比例放大,外汇贵金属波动大更需注意高风险。
基础篇示例用 50~200 次,实盘前应在历史数据上回测,参数过多时倾向取上限但会拖慢速度。