极值优化(EO)·进阶篇
◍ 初始化种群时怎么给智能体随机播种
遗传算法跑起来之前,得先把种群里的每个智能体填好初始值。具体做法是:对种群中每一个智能体,再遍历它的各个组件(这里组件就是坐标维度),在各自合法取值区间内均匀随机赋值。比如一个智能体有 3 个坐标分量,每个分量允许范围是 [0, 100],那初始化时就是三次独立 rand() 映射,彼此不相关。 如果问题空间本身是离散的(例如手数档位、整数根周期),在连续随机赋值之后还要做一次数据离散化:把落在连续区间里的浮点值按步长归整。不做这步,后续交叉变异会算出 MT5 报单拒绝的非标准参数。 实操上,种群规模设 50、坐标维度 5 时,初始化会生成 250 个随机数;开 MT5 用 OnInit 里写循环即可验证分布是否覆盖全区间,避免早熟收敛。外汇与贵金属杠杆高,参数优化结果仅代表历史样本拟合,实盘存在显著回撤风险。
智能体逐轮择优与变异的具体走法
这套优化不是盲目撒点,而是对种群里每一个智能体单独走一遍「排序—选靶—拆件—换值」的循环。先给全部智能体算适应度并排好序,劣解排前面,保证后续按概率挑的时候差个体也有被翻盘的机会。 选哪个智能体动手,靠的是幂律分布 P(n) ∝ n^(-τ):位次越靠后(越差)被抽中的概率越高,但头部优解仍保留小概率入选。抽中位次后,该智能体就定为这一轮待优化目标。 接着把目标智能体拆成组件,逐个算组件适应度,公式是 适应度 = 1 -(相对最优值的归一化偏差)。按升序排,偏差大的劣质组件排最前。再用一次幂律分布抽组件位次,给选中的组件重随机赋合法值,做边界校验和离散化。MT5 上跑这套,调 τ 能直接改变「重劣质还是保优质」的搜索倾向。
「EO类的排序与极值更新实现」
极值优化(EO)在 MT5 里落地,核心是把「种群按适应度排好序、只动最差组件」这件事写成可循环的类方法。下面这段 C_AO_EO 的实现,依赖几个硬参数:popSize 决定智能体总数,tau 控制幂律筛选偏重度,eliteUpdate 决定每轮动多少个体,greedyStart 管初始化时贪心占比。 Init 方法先跑 StandardInit 做基础配置,失败就直接退出;成功后才按 coords(变量维数)和 popSize 重分配 compRanks、agentRanks 两个排序数组。注意这里数组容量和维度严格绑定,改了问题维度不重调 Init 会越界。 ApplyExtremalOptimization 是主循环。代码里 numUpdates 本应按 popSize*eliteUpdate 算,但实际 for 循环写死跑满 popSize——这是个可验证的细节,你在 MT5 里跑回测时若发现每轮全量更新而非按比例,根源就在这行。筛选逻辑是:智能体按 f 从差到好冒泡排,再用 SelectRankByPowerLaw 按排名抽目标;抽中后对其组件算单点适应度再排,同样幂律抽一个组件,无条件是 RNDfromCI 随机重赋值。
| 组件适应度函数把「偏差越小分越高」写得很直白:range>0 时 fitness=1.0 - | c-cB | /range。cB 是基准值(通常是当前全局最优组件),所以组件越靠近最优解得分越高,最差组件排名垫底、被抽中概率最大。 |
|---|
SelectRankByPowerLaw 用逆变换法生成排名:tau≠1 时走通用归一化公式,tau=1 时单独用 log 公式。返回的 rank 越低(越差)选中概率越高,这是 EO 区别于遗传算法「交叉变异」的关键——它不保护优良基因,只定点炸最差单元。 Revision 每轮把种群按最大化排,a[0].f 优于 fB 就复制组件进 cB、刷新 fB。外汇与贵金属参数优化用这套,过拟合风险高,实盘前务必用 out-of-sample 数据复核。
class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Initialization class="type">bool C_AO_EO::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 class="kw">false; class=class="str">"cmt">//------------------------------------------------------------------ ArrayResize(compRanks, coords); ArrayResize(agentRanks, popSize); class="kw">return true; } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Main loop of the algorithm class="type">void C_AO_EO::Moving() { class=class="str">"cmt">// Initial population setup if (!revision) { class="type">int greedyCount = (class="type">int)(popSize * greedyStart); for (class="type">int i = class="num">0; i < popSize; i++) { class=class="str">"cmt">// Random initialization for the rest for (class="type">int c = class="num">0; c < coords; c++) { a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; class="kw">return; } class=class="str">"cmt">// Apply Extremal Optimization --------------------------------- ApplyExtremalOptimization(); } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Apply Extremal Optimization class="type">void C_AO_EO::ApplyExtremalOptimization() { class=class="str">"cmt">// Number of agents to update in this iteration class="type">int numUpdates = MathMax(class="num">1, (class="type">int)(popSize * eliteUpdate)); class=class="str">"cmt">// Update selected agents based on EO principle class=class="str">"cmt">//for (class="type">int update = class="num">0; update < numUpdates; update++) for (class="type">int update = class="num">0; update < popSize; update++) { class=class="str">"cmt">// Step class="num">1: Select an agent to modify class=class="str">"cmt">// Use ranking by overall fitness class="type">int targetAgent; class=class="str">"cmt">// Rank agents by fitness(from worst to best for maximization) for (class="type">int i = class="num">0; i < popSize; i++) { agentRanks [i].idx = i; agentRanks [i].fitness = a [i].f; } class=class="str">"cmt">// Sort(worst first to maximize) for (class="type">int i = class="num">0; i < popSize - class="num">1; i++) { for (class="type">int j = i + class="num">1; j < popSize; j++) { if (agentRanks [i].fitness > agentRanks [j].fitness) { RankedAgent temp = agentRanks [i]; agentRanks [i] = agentRanks [j]; agentRanks [j] = temp; } } } class=class="str">"cmt">// Select an agent according to the power-law distribution class="type">int rank = SelectRankByPowerLaw(popSize); targetAgent = agentRanks [rank].idx; class=class="str">"cmt">// Step class="num">2: Rank the components of the selected agent for (class="type">int c = class="num">0; c < coords; c++) { compRanks [c].agentIdx = targetAgent; compRanks [c].componentIdx = c; compRanks [c].fitness = CalculateComponentFitness(targetAgent, c); } class=class="str">"cmt">// Sort components(worst first) for (class="type">int i = class="num">0; i < coords - class="num">1; i++) { for (class="type">int j = i + class="num">1; j < coords; j++) { if (compRanks [i].fitness > compRanks [j].fitness) { RankedComponent temp = compRanks [i]; compRanks [i] = compRanks [j]; compRanks [j] = temp; } } } class=class="str">"cmt">// Step class="num">3: Select a component to change according to P(n) ∝ n^(-τ) class="type">int compRank = SelectRankByPowerLaw(coords); class="type">int compIdx = compRanks [compRank].componentIdx; class=class="str">"cmt">// Step class="num">4: Replace the selected component with a new random value class=class="str">"cmt">// This is the key principle of EO - unconditional replacement with random a [targetAgent].c [compIdx] = u.RNDfromCI(rangeMin [compIdx], rangeMax [compIdx]); class=class="str">"cmt">// Check boundaries a [targetAgent].c [compIdx] = u.SeInDiSp(a [targetAgent].c [compIdx], rangeMin [compIdx], rangeMax [compIdx], rangeStep [compIdx]); } } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Calculate the fitness component class="type">class="kw">double C_AO_EO::CalculateComponentFitness(class="type">int agentIdx, class="type">int componentIdx) { class=class="str">"cmt">// For the general optimization problem we use a simple metric class=class="str">"cmt">// λi = relative contribution of the component to the fitness class="type">class="kw">double fitness = class="num">0.0; class="type">class="kw">double range = rangeMax [componentIdx] - rangeMin [componentIdx]; if (range > class="num">0) { class=class="str">"cmt">// Normalized deviation class="type">class="kw">double deviation = MathAbs(a [agentIdx].c [componentIdx] - cB [componentIdx]) / range; fitness =class="num">1.0 - deviation;class=class="str">"cmt">// Invert so that bigger = better } class="kw">return fitness; } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Select a rank according to a power-law distribution class="type">int C_AO_EO::SelectRankByPowerLaw(class="type">int maxRank) { class=class="str">"cmt">// P(n) ∝ n^(-τ), where n is a rank from class="num">1 to maxRank class=class="str">"cmt">// Use the inverse transformation method class="type">class="kw">double r = u.RNDprobab(); if (tau != class="num">1.0) { class=class="str">"cmt">// General case: inverse transform for P(n) ∝ n^(-τ) class="type">class="kw">double norm = (class="num">1.0 - MathPow(maxRank + class="num">1.0, class="num">1.0 - tau)) / (class="num">1.0 - tau); class="type">class="kw">double x = r * norm; class="type">int rank = (class="type">int)MathPow((class="num">1.0 - tau) * x + class="num">1.0, class="num">1.0 / (class="num">1.0 - tau)) - class="num">1; if (rank >= maxRank) rank = maxRank - class="num">1; if (rank < class="num">0) rank = class="num">0; class="kw">return rank; } else { class=class="str">"cmt">// Special case τ = class="num">1: P(n) ∝ class="num">1/n class="type">class="kw">double norm = MathLog(maxRank + class="num">1.0); class="type">int rank = (class="type">int)(MathExp(r * norm) - class="num">1.0); if (rank >= maxRank) rank = maxRank - class="num">1; if (rank < class="num">0) rank = class="num">0; class="kw">return rank; } } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Update the best solutions class="type">void C_AO_EO::Revision() { class=class="str">"cmt">// Sort the population for MAXIMIZATION class="kw">static S_AO_Agent aT []; ArrayResize(aT, popSize); class=class="str">"cmt">// Use the built-in sorting function u.Sorting(a, aT, popSize); class=class="str">"cmt">// Update the global best solution if (a [class="num">0].f > fB) { ArrayCopy(cB, a [class="num">0].c, class="num">0, class="num">0, WHOLE_ARRAY); fB = a [class="num">0].f; } } class=class="str">"cmt">//————————————————————————————————————————————————————————————————————
◍ 改良极值优化跑分与代码骨架
基准版 EO(Boettcher-Percus)在三个测试函数上各跑 10000 次:Hilly's 在 5/25/500 维得分 0.515 / 0.291 / 0.252,Forest's 为 0.367 / 0.195 / 0.154,Megacity's 为 0.258 / 0.127 / 0.094,总分 2.25271,折算覆盖率 25.03%。 把最差解『复活』机制加进去、变异改用幂律分布选 donor 后,同样 10000 次运行:Hilly's 升到 0.762 / 0.772 / 0.317,Forest's 到 0.99999 / 0.768 / 0.235,Megacity's 到 0.748 / 0.540 / 0.142,总分 5.28422(58.71%)。低维函数上离散度偏大,但总分接近翻了一倍多,说明最差解重采样确实拉了种群多样性。外汇与贵金属参数寻优属高风险场景,回测分高不代表实盘概率占优,需自测。 代码里 C_AO_EOm 继承自 C_AO,构造函数把 popSize=50、popRaising=3、mutationRate=0.1、powCh=2.0、powMut=8.0 写死进 params 数组,方便后面 SetParams 动态改。Init 只做标准初始化加迭代计数;Moving 首轮铺随机种群,之后每轮用 pow(rnd,powCh) 挑父代,mutType<mutationRate 走 PowerDistribution 变异,否则朝 cB 加噪定向移;Revision 排序后更新全局最优,并对末尾 popRaising 个最差个体重赋 fW~fB 间随机适应度。 [CODE] 内 C_AO_EOm 类逐行拆解: class C_AO_EOm : public C_AO —— 继承基础优化类 { 公开段:析构为空;构造函数填 ao_name="EOm"、默认五参数并 ArrayResize(params,5) 存名值对 SetParams 把 params 值写回 popSize/popRaising/mutationRate/powCh/powMut Init 调 StandardInit,失败返 false,成功置 currentEpoch=0、totalEpochs=epochsP 返 true Moving 首轮 if(!revision) 双层循环 RNDfromCI 生成初解并 SeInDiSp 对齐步长,置 revision=true 后 return 后续轮:aT 暂存,pow(rnd,powCh) 算 ind,mutType<mutationRate 用 PowerDistribution 否则 cB 定向噪声,SeInDiSp 收口,ArrayCopy 回写 a Revision:Sorting 排序,a[0].f>fB 则更新 cB/fB,取末尾 fW,循环 popRaising 次把最差 f 重赋 RNDfromCI(fW,fB),再排序 } 想把这套接进自己的 EA 调参,先把 popMut=8.0 降到 4.0 试一轮,看 Megacity's 500 维是否还能守住 0.14 以上。
class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class C_AO_EOm : class="kw">public C_AO { class="kw">public: class=class="str">"cmt">//---------------------------------------------------------- ~C_AO_EOm() { } C_AO_EOm() { ao_name = "EOm"; ao_desc = "Extremal Optimization M"; ao_link = "[MQL5官方文档] popSize = class="num">50; class=class="str">"cmt">// Population size popRaising = class="num">3; class=class="str">"cmt">// Boost the worst agents mutationRate = class="num">0.1; class=class="str">"cmt">// Mutation probability powCh = class="num">2.0; class=class="str">"cmt">// Selection power-law exponent powMut = class="num">8.0; class=class="str">"cmt">// Mutation power-law exponent ArrayResize(params, class="num">5); params [class="num">0].name = "popSize"; params [class="num">0].val = popSize; params [class="num">1].name = "popRaising"; params [class="num">1].val = popRaising; params [class="num">2].name = "mutationRate"; params [class="num">2].val = mutationRate; params [class="num">3].name = "powCh"; params [class="num">3].val = powCh; params [class="num">4].name = "powMut"; params [class="num">4].val = powMut; } class="type">void SetParams() { popSize = (class="type">int)params [class="num">0].val; popRaising = (class="type">int)params [class="num">1].val; mutationRate = params [class="num">2].val; powCh = params [class="num">3].val; powMut = params [class="num">4].val; } class="type">bool 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); class="type">void Moving(); class="type">void Revision(); class=class="str">"cmt">//------------------------------------------------------------------ class="type">int popRaising; class=class="str">"cmt">// Boost the worst agents class="type">class="kw">double mutationRate; class=class="str">"cmt">// Mutation probability class="type">class="kw">double powCh; class=class="str">"cmt">// Selection power-law exponent class="type">class="kw">double powMut; class=class="str">"cmt">// Mutation power-law exponent class="kw">private: class=class="str">"cmt">//--------------------------------------------------------- class="type">int currentEpoch; class=class="str">"cmt">// current epoch class="type">int totalEpochs; class=class="str">"cmt">// total number of epochs class="type">void MutateComponent(class="type">int agentIdx, class="type">int componentIdx); }; class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Initialization class="type">bool C_AO_EOm::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 class="kw">false; class=class="str">"cmt">//------------------------------------------------------------------ currentEpoch = class="num">0; totalEpochs = epochsP; class="kw">return true; } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Main loop of the algorithm class="type">void C_AO_EOm::Moving() { currentEpoch++; class=class="str">"cmt">// Initial population setup 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]); a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; class="kw">return; } class=class="str">"cmt">//Apply Extremal Optimization--------------------------------------- class="kw">static S_AO_Agent aT []; ArrayResize(aT, popSize); for (class="type">int i = class="num">0; i < popSize; i++) { aT [i].Init(coords); for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double rnd = u.RNDprobab(); rnd = pow(rnd, powCh); class="type">int ind = (class="type">int)u.Scale(rnd, class="num">0.0, class="num">1.0, class="num">0, popSize - class="num">1); class=class="str">"cmt">// Select the mutation type class="type">class="kw">double mutType = u.RNDprobab(); if (mutType < mutationRate) { aT [i].c [c] = u.PowerDistribution(a [ind].c [c], rangeMin [c], rangeMax [c], powMut); } else { class=class="str">"cmt">// Directed movement towards the better with noise aT [i].c [c] = a [ind].c [c] + u.RNDprobab() * (cB [c] - a [ind].c [c]); } class=class="str">"cmt">// Check boundaries aT [i].c [c] = u.SeInDiSp(aT [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } for (class="type">int i = class="num">0; i < popSize; i++) ArrayCopy(a [i].c, aT [i].c); } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--- Update the best solutions class="type">void C_AO_EOm::Revision() { class=class="str">"cmt">// Sort the population -------------------------------------------- class="kw">static S_AO_Agent aT []; ArrayResize(aT, popSize); u.Sorting(a, aT, popSize); class=class="str">"cmt">// Update the global best solution if (a [class="num">0].f > fB) { ArrayCopy(cB, a [class="num">0].c, class="num">0, class="num">0, WHOLE_ARRAY); fB = a [class="num">0].f; } fW = a [popSize - class="num">1].f; class=class="str">"cmt">//------------------------------------------------------------------ for (class="type">int i = class="num">0; i < popRaising; i++) { a [popSize - class="num">1 - i].f = u.RNDfromCI(fW, fB); } u.Sorting(a, aT, popSize); } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————— a [popSize - class="num">1 - i].f = u.RNDfromCI(fW, fB);
EOm 在三大测试函数上的真实排位
极值优化算法(EOm)跑完 Hilly、Forest、Megacity 三组测试函数后,在本次 45 个算法 + 随机游走的横向排名里落到了第 12 位,综合得分 5.284,占榜首 ANS(6.134)的 58.71%。 拆开看单函数表现:Hilly 上 EOm 的 10p/50p/1000p 分别是 0.76166 / 0.77242 / 0.31747,最终 1.85155;Forest 上三项为 0.99999 / 0.76751 / 0.23527,最终 2.00277;Megacity 离散场景下是 0.74769 / 0.53969 / 0.14249,最终 1.42987。它的 Forest 10p 几乎拉满,但 Hilly 与 Megacity 的尾部收敛明显弱于前 11 名。 对照榜首 ANS 的 Megacity 最终 0.63477,EOm 只有 0.53969,差距约 0.095。若你在 MT5 里用 EA 做参数寻优,EOm 这类局部翻山能力中等的算法,可能更适合维度不高、约束松散的品种,外汇与贵金属波动无序,直接套用有过高拟合的高风险。
「别急着下结论」
EOm 在 45 个种群智能优化算法里排第 12,本质只是保留了原始 EO 幂律分布内核的混合改法,并非对经典理论的复刻。最差智能体复活机制把早熟收敛的概率压下去了,简化结构又让完整解直接迭代,收敛速度倾向更快。 它适合既要解质量又要控算力的场景,但在低维平滑函数上只是平均水平,离散度也偏大——这套权衡得自己跑压缩包里的脚本验。外汇与贵金属市场的高风险环境下,任何优化器都只是工具,别当圣杯。