老鹰策略(ES)(基础篇)
◍ 老鹰策略在 MT5 上的落地雏形
老鹰策略(Eagle Strategy,简称 ES)是一套面向 MetaTrader 5 平台的算法交易思路,由 Andrey Dik 在 2026 年 6 月 29 日公开发布,截至发稿页面记录浏览量为 227 次、评论数为 0,说明该策略尚处于早期扩散阶段,未进入大众回测验证期。 从结构看,原文给出的骨架只包含「引言、算法的实现、测试结果」三个区块,没有展开参数与品种适配说明。对外汇与贵金属交易者而言,这类未附样本外数据的早期算法具有高不确定性,实盘前必须在 MT5 策略测试器中跑通至少近 3 年 tick 级回测。 打开 MT5 后,可先在「导航器—专家顾问」中新建空白 EA,对照后续小节补入 ES 的核心逻辑;当前能确定的动作只有一件:把原作者的算法实现段落逐行转写成 MQL5 函数,避免直接套用未经验证的黑箱 dll。
为什么传统优化在EA调参里容易翻车
做 MT5 的 EA 参数优化,本质是在一个高维、离散又带噪声的空间里找最优解。网格遍历和朴素遗传算法经常卡在局部洼地——比如某组止损止盈在 EURUSD 回测里夏普看起来不错,但换到 XAUUSD 就直接回撤爆掉,这说明它根本没摸到全局结构。 元启发式算法这几年被大量搬进量化圈,原因是它们不靠穷举,而是用某种「探索—利用」的平衡去跳出局部。老鹰策略(Eagle Strategy)就是其中一类:它模拟猛禽先在高空靠视觉广域扫猎、再俯冲精准追捕的行为,把全局探索和局部求精拆成两个阶段跑。 对外汇和贵金属交易者来说,这类算法直接价值在降低过拟合概率,但高杠杆品种本身高风险,任何优化结果都只是「历史样本上的概率倾向」,拿到实盘前必须做样本外验证。
「老鹰策略的两阶段搜索与MT5落地」
老鹰策略(ES)把优化拆成两个阶段:先用莱维飞行做全局扫场,发现较优区域后切到萤火虫式局部搜索。莱维飞行不是均匀随机游走,而是大量小步加偶发超长跳跃,幂律分布让它在广域探索和局部细化之间自然平衡。 核心切换逻辑很直白:全局阶段若刷新最优解,就进局部超球(半径默认0.1)用萤火虫吸引细化;连续5代无改进则减小λ做更激进飞行;局部跑满20代再回全局。这样周期性交替能躲开局部最优陷阱。 在MT5里落地时,C_AO_ES类继承自C_AO。Init里prevBestFitness设成-DBL_MAX保证首解必录,epochMax接epochsP控制总代数,gamma_es固定1.0供萤火虫用。全局阶段stepScale随世代递减,初期步长大、后期收窄。 莱维步长靠曼特尼亚算法生成:取两个高斯变量u和v,levy = u / v^(1/λ),σ按Γ函数算。代码对v_abs做零保护、对步长绝对值截断防暴冲。下面这段是GenerateLevyStep里算σ与步长的关键部分,可直接抄进EA验证。 别把λ当固定常量:停滞期主动调小它,全局跳跃会更野,适合多峰函数;若你的参数空间很平,反而容易飞过头。
class="type">class="kw">double sigma = MathPow(Gamma(class="num">1.0 + lambda) * MathSin(MathPi() * lambda / class="num">2.0) / (Gamma((class="num">1.0 + lambda) / class="num">2.0) * lambda * MathPow(class="num">2.0, (lambda - class="num">1.0) / class="num">2.0)), class="num">1.0 / lambda); class="type">class="kw">double u_val = u.Gaussian(class="num">0.0, sigma * sigma); class="type">class="kw">double v_val = u.Gaussian(class="num">0.0, class="num">1.0); v_val = MathAbs(v_val) + class="num">1e-12; levyStep[c] = u_val / MathPow(v_val, class="num">1.0 / lambda); levyStep[c] = MathMin(MathAbs(levyStep[c]), class="num">10.0) * (levyStep[c] < class="num">0 ? -class="num">1.0 : class="num">1.0);
◍ 正态采样与伽马近似怎么喂给莱维步长
GenerateGaussian 用 Box-Muller 变换产标准正态分布随机数,均值 0、标准差 1。它靠静态变量 hasSpare 和 spare 一次算两个独立样本,把其中一个存为备用,下次调用直接取,省掉一半对数运算。 对零取对数会炸,所以 u_val 加了 1e-10 再进 Math.log。幅值 mag 取 -2.0*Math.log(u_val+1e-10) 的平方根,返回 mag*Math.sin(2π*v_val),备用数则是 mag*Math.cos(2π*v_val)。 Gamma 方法用 Lanczos 近似算伽马函数,z<0.5 时切欧拉反射公式 Γ(z)Γ(1-z)=π/sin(πz) 把参数翻到安全区间。固定系数换精度,只做少量算术和查表,覆盖广、成本低,够优化类实盘脚本用。 下面这段代码把两者接起来算 Mantegna 莱维步长:sigma 由 Γ(1+λ) 与 Γ((1+λ)/2) 的比值推出,u_val 和 v_val 都来自 GenerateGaussian,最终 levyStep = u_val / v_val^(1/λ)。外汇与贵金属波动有肥尾,这类步长在 EA 参数搜索里可能比纯均匀随机更快收敛,但回测不保证实盘同效,属高风险用法。
class=class="str">"cmt">// Calculating sigma for Mantegna algorithm class="type">class="kw">double numerator = Gamma(class="num">1.0 + lambda) * MathSin(M_PI * lambda / class="num">2.0); class="type">class="kw">double denominator = Gamma((class="num">1.0 + lambda) / class="num">2.0) * lambda * MathPow(class="num">2.0, (lambda - class="num">1.0) / class="num">2.0); class="type">class="kw">double sigma = MathPow(numerator / denominator, class="num">1.0 / lambda); class=class="str">"cmt">// Generate u and v from normal distributions class="type">class="kw">double u_val = GenerateGaussian() * sigma; class="type">class="kw">double v_val = MathAbs(GenerateGaussian()); class=class="str">"cmt">// Calculate Levy step levyStep[c] = u_val / MathPow(v_val, class="num">1.0 / lambda); σ = [Γ(class="num">1+λ) × sin(πλ/class="num">2) / (Γ((class="num">1+λ)/class="num">2) × λ × class="num">2^((λ-class="num">1)/class="num">2))]^(class="num">1/λ) Γ(z+class="num">1) = √(2π) × ((z+g+class="num">0.5)^(z+class="num">0.5)) × e^(-(z+g+class="num">0.5)) × Ag(z) Γ(z) × Γ(class="num">1-z) = π / sin(πz) step = u / |v|^(class="num">1/λ) class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class C_AO_ES : class="kw">public C_AO { class="kw">public: class=class="str">"cmt">//---------------------------------------------------------- ~C_AO_ES() { } C_AO_ES() { ao_name = "ES"; ao_desc = "Eagle Strategy"; ao_link = "[MQL5官方文档] popSize = class="num">100; class=class="str">"cmt">// population size lambda = class="num">1.0; class=class="str">"cmt">// Levy distribution parameter(class="num">1 < λ ≤ class="num">3) sphereRadius = class="num">0.1; class=class="str">"cmt">// hypersphere radius for local search localIterations = class="num">20; class=class="str">"cmt">// number of local search iterations alpha = class="num">0.1; class=class="str">"cmt">// randomization parameter for Firefly beta0 = class="num">1.2; class=class="str">"cmt">// initial attractiveness ArrayResize(params, class="num">6); params [class="num">0].name = "popSize"; params [class="num">0].val = popSize; params [class="num">1].name = "lambda"; params [class="num">1].val = lambda; params [class="num">2].name = "sphereRadius"; params [class="num">2].val = sphereRadius; params [class="num">3].name = "localIterations"; params [class="num">3].val = localIterations; params [class="num">4].name = "alpha"; params [class="num">4].val = alpha; params [class="num">5].name = "beta0"; params [class="num">5].val = beta0; } class="type">void SetParams() {
火蝇算法类的参数与私有跟踪字段
上面这段声明把火蝇(Firefly)式进化策略的核心可调项直接摊开了:种群规模 popSize 与局部迭代次数 localIterations 被强转成 int,而 lambda、sphereRadius、alpha、beta0 均从 params 数组按索引 0~5 读取,属于浮点超参。 lambda 的注释写明取值范围是 1 < λ ≤ 3,这是 Lévy 飞行步长的分布参数;sphereRadius 决定局部搜索超球半径,beta0 是初始吸引力,alpha 控制随机扰动强度。调参时若 lambda 取接近 3 的值,全局跳跃会更稀疏但步长偏长, EURUSD 这类高杠杆品种上容易因跳变引发夸张回撤,属典型高风险场景。 私有段里有一组容易被忽略的收敛监控字段:prevBestFitness 与 stagnationCounter 配合,能在适应度连续不更新时触发阶段切换;inLocalSearchPhase 加 localSearchCenter / localSearchCounter 实现局部开采的定位与计数。想验证效果,直接在 MT5 里把 epochMax 设小(比如 50),观察 stagnationCounter 何时被激活即可。
popSize = (class="type">int)params [class="num">0].val; lambda = params [class="num">1].val; sphereRadius = params [class="num">2].val; localIterations = (class="type">int)params [class="num">3].val; alpha = params [class="num">4].val; beta0 = params [class="num">5].val; } class="type">bool Init(const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">// minimum values const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">// maximum values const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// step change 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 lambda; class=class="str">"cmt">// Levy distribution parameter(class="num">1 < λ ≤ class="num">3) class="type">class="kw">double sphereRadius; class=class="str">"cmt">// hypersphere radius for local search class="type">int localIterations; class=class="str">"cmt">// number of local search iterations class="type">class="kw">double alpha; class=class="str">"cmt">// randomization parameter class="type">class="kw">double beta0; class=class="str">"cmt">// initial attractiveness class="kw">private: class=class="str">"cmt">//--------------------------------------------------------- class="type">class="kw">double gamma_es; class=class="str">"cmt">// light absorption coefficient class="type">class="kw">double levyStep []; class=class="str">"cmt">// array for Levy steps class=class="str">"cmt">// Phase tracking class="type">bool inLocalSearchPhase; class=class="str">"cmt">// local search flag class="type">int localSearchCenter; class=class="str">"cmt">// local search center class="type">int localSearchCounter; class=class="str">"cmt">// local search iteration counter class=class="str">"cmt">// Monitoring convergence class="type">class="kw">double prevBestFitness; class=class="str">"cmt">// previous best value class="type">int stagnationCounter; class=class="str">"cmt">// stagnation counter class=class="str">"cmt">// Tracking epochs class="type">int epochCurrent; class=class="str">"cmt">// current epoch class="type">int epochMax; class=class="str">"cmt">// maximum number of epochs class=class="str">"cmt">// Auxiliary methods class="type">void GlobalExploration(); class="type">void LocalExploitation(); class="type">void GenerateLevyStep(); class="type">class="kw">double GenerateGaussian(); class="type">class="kw">double Gamma(class="type">class="kw">double z); }; class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class="type">bool C_AO_ES::Init(const class="type">class="kw">double &rangeMinP [], const class="type">class="kw">double &rangeMaxP [],