圆搜索算法(CSA)(基础篇)
圆搜索算法在 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,看回测中早期探索广度是否变化——外汇与贵金属参数优化属高风险操作,结果仅代表历史样本倾向,不代表实盘表现。
<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 { <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--------------------------------------------------------------------</span> C_AO_CSA() { ao_name = <span class="class="type">class="kw">string">"CSA"</span>; ao_desc = <span class="class="type">class="kw">string">"Circle Search Algorithm"</span>; ao_link = <span class="class="type">class="kw">string">"[MQL5官方文档] popSize = <span class="number">class="num">50</span>; <span class="comment">class=class="str">"cmt">// population size</span> constC = <span class="number">class="num">0.8</span>; <span class="comment">class=class="str">"cmt">// optimal value for the exploration phase</span> w = <span class="macro">M_PI</span>; <span class="comment">class=class="str">"cmt">// initial value w</span> aParam = <span class="macro">M_PI</span>; <span class="comment">class=class="str">"cmt">// initial value a</span> p = <span class="number">class="num">1.0</span>; <span class="comment">class=class="str">"cmt">// initial value p</span> theta = <span class="number">class="num">0</span>; <span class="comment">class=class="str">"cmt">// initial value of the angle</span> <span class="functions">ArrayResize</span> (params, <span class="number">class="num">2</span>); params [<span class="number">class="num">0</span>].name = <span class="class="type">class="kw">string">"popSize"</span>; params [<span class="number">class="num">0</span>].val = popSize; params [<span class="number">class="num">1</span>].name = <span class="class="type">class="kw">string">"constC"</span>; params [<span class="number">class="num">1</span>].val = constC; } <span class="keyword">class="type">void</span> SetParams() { popSize = (<span class="keyword">class="type">int</span>)params [<span class="number">class="num">0</span>].val; constC = params [<span class="number">class="num">1</span>].val; } <span class="keyword">class="type">bool</span> Init(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &rangeMinP [], <span class="comment">class=class="str">"cmt">// minimum values</span> <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &rangeMaxP [], <span class="comment">class=class="str">"cmt">// maximum values</span> <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &rangeStepP [], <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 的值可以卡死最大迭代轮数,避免优化器在窄区间内空转。
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++) {