种群优化算法:鲸鱼优化算法(WOA)(基础篇)
「鲸鱼优化算法在MT5里的落地起点」
鲸鱼优化算法(WOA)属于种群优化类方法,核心是用模拟座头鲸包围、气泡网捕食的群体行为去搜解空间。在MT5里它可以脱离具体交易逻辑,先作为一个通用优化器跑起来。 原文示例发布于2024年11月4日14:29,作者Andrey Dik,页面记录显示已有649次查看、2条评论,说明这类算法模块在量化社群里已有基本关注度。 对交易者而言,第一步不是急着接信号,而是把WOA当黑盒优化器,用历史数据验证它收敛到参数的速度是否比网格搜索更省时。外汇与贵金属杠杆高,任何优化结果都只是历史拟合,实盘前须在小布盯盘里做样本外复核。
从座头鲸捕食到元启发式寻优
座头鲸每天可消耗多达 1.4 公吨食物,却在冬季几乎停食,靠丰期积累的脂肪熬过禁食期。这种「富时囤、缺时扛」的节奏,本质是海洋环境不确定下的生存优化,而非随机游荡。 Mirjalili 与 Lewis 在 2016 年提出的鲸鱼优化算法(WOA),直接把这种捕食逻辑抽象成了数学寻优框架。鲸鱼代表解空间里的一个候选解,「气泡网」和「螺旋穿透」两种行为,分别对应包围猎物与螺旋逼近猎物两类位置更新机制。 在 WOA 中,狩猎不再是生物动作,而是寻找最优解的过程:解向当前最优解收缩,或沿对数螺旋向目标绕行。对做外汇 / 贵金属参数寻优的交易者来说,这类元启发式算法的高风险在于——它只保证在给定回测窗口里「可能」收敛,绝不承诺实盘稳健。
◍ 鲸群寻优怎么搬进EA里
座头鲸优化算法(WOA)把一群随机初始化的「鲸鱼」当作候选解,每轮挑出目标函数最优的那头当领导者,其余鲸鱼按它的位置更新。更新分两种模式:探索时绕全局最优随机搜,开发时直接朝最优靠拢。原版 WOA 容易陷局部最优、收敛偏慢,因此在改进版 WOAm 里我硬加了第四阶段「迁移」——随机改变个体位置,模拟鲸鱼换猎场,抗卡滞性明显提升。
| WOAm 的核心节奏由 aKo 控制:aKo = 2.0 - epochNow * (2.0 / epochs),伴随 A = 2.0*aKo*r - aKo(r 为 -1~1 均匀随机数)。A 绝对值大于 1 走探索,小于 1 走开发;再叠一个 spiralProb 概率触发螺旋运动,公式 X = Xprev + | Xprev-X | *exp(b*l)*cos(2*M_PI*l),对应「气泡网」包抄。 |
|---|
下面这段是 WOAm 最底层的智能体结构与类骨架,直接在 MT5 里建新文件就能编译跑通。struct 只存旧坐标和旧适应度,Init 里 fPrev 设成 -DBL_MAX 保证任何评价值都能覆盖;类 C_AO_WOAm 默认 popSize=100,参数数组长度 4,继承基类 C_AO。 别把正态当圣经:迁移阶段用的 PowerDistribution 不是高斯,是幂分布长尾,正是它让远离全局解的个体仍有非零概率被翻出来,避免早熟收敛。开 MT5 把 popSize 改成 30 对比 100,回测曲线会告诉你种群规模对收敛速度的真实影响。
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="keyword">class="kw">struct</span> S_WOA_Agent { <span class="keyword">class="type">class="kw">double</span> cPrev []; <span class="comment">class=class="str">"cmt">//previous coordinates</span> <span class="keyword">class="type">class="kw">double</span> fPrev; <span class="comment">class=class="str">"cmt">//previous fitness</span> <span class="keyword">class="type">void</span> Init(<span class="keyword">class="type">int</span> coords) { <span class="functions">ArrayResize</span> (cPrev, coords); fPrev = -<span class="macro">DBL_MAX</span>; } }; <span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="keyword">class</span> C_AO_WOAm : <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_WOAm() { } C_AO_WOAm() { ao_name = <span class="class="type">class="kw">string">"WOAm"</span>; ao_desc = <span class="class="type">class="kw">string">"Whale Optimization Algorithm M"</span>; popSize = <span class="number">class="num">100</span>; <span class="comment">class=class="str">"cmt">//population size</span> ArrayResize(<span class="keyword">params</span>, <span class="number">class="num">4</span>); <span class="keyword">params</span> [<span class="number">class="num">0</span>].name = <span class="class="type">class="kw">string">"popSize"</span>; <span class="keyword">params</span> [<span class="number">class="num">0</span>].val = popSize; <span class="keyword">params</span> [<span class="number">class="num">1</span>].name = <span class="class="type">class="kw">string">"refProb"</span>; <span class="keyword">params</span> [<span class="number">class="num">1</span>].val = refProb;
「把鲸鱼优化参数接进实盘初始化」
上面这段是鲸鱼优化算法(WOA)封装类的尾部定义与 Init 实现,核心是把四个外部参数落进代理群。params[0] 到 params[3] 分别承载种群规模、refinement 概率、螺旋系数与螺旋概率,SetParams 里直接按索引取值赋给类成员,改一个外部输入就能换一整套寻优行为。 Init 函数先跑 StandardInit 做边界与步长校验,失败直接返回 false;通过后把 epochsP 存进 epochs、epochNow 归零,再用 ArrayResize 按 popSize 扩出 agent 向量,循环里给每个代理调 Init(coords)。popSize 若为 50,这里就实打实建出 50 个搜索个体。 Moving 里 epochNow 自增,未进入 revision 分支时,对每个代理、每个坐标维度用 u.RNDfromCI 在 [rangeMin[c], rangeMax[c]] 内撒随机初值。外汇与贵金属波动剧烈,这类群体寻优若直接接 EA 跑实盘,参数区间设宽了可能收敛慢、设窄了易陷局部极值,属高风险用法,建议先 MT5 策略测试器回测。
params [class="num">2].name = "spiralCoeff"; params [class="num">2].val = spiralCoeff; params [class="num">3].name = "spiralProb"; params [class="num">3].val = spiralProb; } class="type">void SetParams() { popSize = (class="type">int)params [class="num">0].val; refProb = params [class="num">1].val; spiralCoeff = params [class="num">2].val; spiralProb = params [class="num">3].val; } class="type">bool Init(const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">//minimum search range const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">//maximum search range const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search 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 refProb; class=class="str">"cmt">//refinement probability class="type">class="kw">double spiralCoeff; class=class="str">"cmt">//spiral coefficient class="type">class="kw">double spiralProb; class=class="str">"cmt">//spiral probability S_WOA_Agent agent []; class=class="str">"cmt">//vector class="kw">private: class=class="str">"cmt">//------------------------------------------------------------------- class="type">int epochs; class="type">int epochNow; }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_WOAm::Init(const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">//minimum search range const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">//maximum search range const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search const class="type">int epochsP = class="num">0) class=class="str">"cmt">//number of epochs { if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return false; class=class="str">"cmt">//---------------------------------------------------------------------------- epochs = epochsP; epochNow = class="num">0; ArrayResize(agent, popSize); for (class="type">int i = class="num">0; i < popSize; i++) agent [i].Init(coords); class="kw">return true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_WOAm::Moving() { epochNow++; 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++) { a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);