辩证搜索(DA)·进阶篇
◍ 代理寻优里的距离与坐标工具
Dragonfly Algorithm 在 MT5 里跑群体寻优,先得有一组底层辅助函数撑住几何运算。EuclideanDistance 算多维空间两点的欧氏距离,u.RNDfromCI 从给定区间抽随机数,u.SeInDiSp 把连续值按步长对齐到合法网格,u.RNDprobab 则返回均匀概率分布的随机量。 Revision 方法负责把每个代理找到的解往更优方向推。它用 for 循环扫一遍种群:若某代理适应度 a[i].f 大于全局最优 fB,就刷新全局解并记录代理索引 ind;同时每个代理的 f 若优于自身历史最佳,也把当前坐标 c 拷进个体最优 cB。 当 ind 不为 -1,说明出现了新全局最优,对应代理坐标被写进全局 cB。方法末尾建 aT 数组并按种群规模 resize,再调 u.Sorting_fB 依据 fB 对代理排座次,方便下一轮迭代直接挑头部个体。 下面这段 Moving 是代理在搜索空间里挪位置的核心。首次进入(revision 为 false)时随机撒点:每个代理各维先用 RNDfromCI 取区间随机值,再经 SeInDiSp 吸附到步长网格,之后置 revision=true 并返回。 后续调用则先更新‘最优思考者’(索引0):其新位置 = 自身历史最优 + 均匀随机数乘邻代理偏移,同样过一遍步长对齐。接着对投机思考者(k2 到 k1 区间)分别向前 k2 个、向后 k2 个邻居算欧氏距离,抓出最远的前邻与后邻,谁更远就选谁来驱动位移更新。外汇与贵金属参数寻优高风险,回测过拟合概率不低,建议开 MT5 用自己品种数据跑一遍验证。
class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">// Implement agent movement in the search space class="type">void C_AO_DA::Moving() { class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">// Initialize the agents&class="macro">#x27; positions randomly 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">//---------------------------------------------------------------------------- class=class="str">"cmt">// Update the best thinker&class="macro">#x27;s position for (class="type">int c = class="num">0; c < coords; c++) { a [class="num">0].c [c] = a [class="num">0].cB [c] + u.RNDprobab() * (a [class="num">1].c [c] - a [class="num">0].c [c]); a [class="num">0].c [c] = u.SeInDiSp(a [class="num">0].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">class="kw">double dist_next = -DBL_MAX; class=class="str">"cmt">// maximum distance to the next neighbor class="type">class="kw">double dist_prev = -DBL_MAX; class=class="str">"cmt">// maximum distance to the previous neighbor class="type">class="kw">double dist = class="num">0.0; class=class="str">"cmt">// current distance class="type">int antiNextIND = class="num">0; class=class="str">"cmt">// index of the most distant next neighbor class="type">int antiPrevIND = class="num">0; class=class="str">"cmt">// index of the most distant previous neighbor class="type">int antiIND = class="num">0; class=class="str">"cmt">// selected index to update position class=class="str">"cmt">// Update the positions of speculative thinkers ------------------------------- for (class="type">int i = k2; i < k1; i++) { class=class="str">"cmt">// Find the most distant previous neighbor for (class="type">int j = class="num">1; j <= k2; j++) { dist = EuclideanDistance(a [i].cB, a [i - j].cB, coords); if (dist > dist_prev) { dist_prev = dist; antiPrevIND = i - j; } } class=class="str">"cmt">// Find the farthest next neighbor for (class="type">int j = class="num">1; j <= k2; j++) { dist = EuclideanDistance(a [i].cB, a [i + j].cB, coords); if (dist > dist_next) { dist_next = dist; antiNextIND = i + j; } } class=class="str">"cmt">// Select the most distant neighbor to update position if (dist_prev > dist_next) antiIND = antiPrevIND;
「两类思考者的位置更新与最优解留存」
这段逻辑把群体拆成「投机型」和「务实型」两类 agent,各自用不同随机机制刷新坐标。投机型在循环里走 u.RNDbool(),意味着以 0/1 二值概率直接跳向反例个体的维度差值;被注释掉的 u.RNDprobab() 版本才是连续比例混合,实盘切换这两行能明显改变收敛抖动。 务实型从 k1 到 popSize 遍历,先随机抽两个投机型个体,用欧几里得距离比较远近,只朝更近的那个更新。它的坐标步进固定用 u.RNDprobab() 乘差值,比投机型更平滑,回测中这类写法在 30 维参数空间里通常收敛更稳定。 Revision() 负责把每次迭代的成果落袋:逐个 agent 比 f 和 fB,刷新全局最优 fB 与个人最优 cB,并用 ArrayCopy 把坐标存进 cB。最后按 fB 排序,静态数组 aT 承接排序结果,方便下一轮直接切分 k1。外汇与贵金属参数优化属高风险,过拟合概率偏高,建议 MT5 策略测试器里先跑 3 个月 Tick 数据验证。
else antiIND = antiNextIND; class=class="str">"cmt">// Update the speculative thinker&class="macro">#x27;s coordinates for (class="type">int c = class="num">0; c < coords; c++) { a [i].c [c] = a [i].cB [c] + u.RNDbool() * (a [antiIND].c [c] - a [i].c [c]); class=class="str">"cmt">//a [i].c [c] = a [i].cB [c] + u.RNDprobab() * (a [antiIND].c [c] - a [i].c [c]); a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } class=class="str">"cmt">// Update the positions of practical thinkers -------------------------------- for (class="type">int i = k1; i < popSize; i++) { class=class="str">"cmt">// Random selection of two speculative thinkers antiNextIND = u.RNDintInRange(class="num">0, k1 - class="num">1); antiPrevIND = u.RNDintInRange(class="num">0, k1 - class="num">1); if (antiNextIND == antiPrevIND) antiNextIND = antiPrevIND + class="num">1; class=class="str">"cmt">// Calculate distances to selected thinkers dist_next = EuclideanDistance(a [i].cB, a [antiNextIND].cB, coords); dist_prev = EuclideanDistance(a [i].cB, a [antiPrevIND].cB, coords); class=class="str">"cmt">// Select the closest thinker to update the position if (dist_prev < dist_next) antiIND = antiPrevIND; else antiIND = antiNextIND; class=class="str">"cmt">// Update the coordinates of the practical thinker for (class="type">int c = class="num">0; c < coords; c++) { a [i].c [c] = a [i].cB [c] + u.RNDprobab() * (a [antiIND].c [c] - a [i].c [c]); a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">// Review and update the best solutions found class="type">void C_AO_DA::Revision() { class="type">int ind = -class="num">1; class=class="str">"cmt">// Update the best solutions found for each agent for (class="type">int i = class="num">0; i < popSize; i++) { class=class="str">"cmt">// Update the global best solution if (a [i].f > fB) { fB = a [i].f; ind = i; } class=class="str">"cmt">// Update the agent&class="macro">#x27;s personal best solution if (a [i].f > a [i].fB) { a [i].fB = a [i].f; ArrayCopy(a [i].cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } } class=class="str">"cmt">// Update the global best solution coordinates if (ind != -class="num">1) ArrayCopy(cB, a [ind].c, class="num">0, class="num">0, WHOLE_ARRAY); class=class="str">"cmt">// Sort agents by their best found solutions class="kw">static S_AO_Agent aT []; ArrayResize(aT, popSize); u.Sorting_fB(a, aT, popSize); }
DA 跑分:布尔扰动带来的意外跃升
把辩证算法(DA)丢进 Hilly、Forest、Megacity 三组标准测试函数里各跑 1 万次,初版用 [0.0;1.0] 均匀随机数,总分只有 3.36185(折算 37.35%),在榜单里排不上号。 问题出在随机源:原实现误用了随机布尔值,坐标有 50% 概率保持原状、50% 概率被对立面坐标替换。这种非连续扰动反而更贴近「正题—反题」对立的设定,实测总分跳到 5.21560(57.95%),整体有效率提升约 20.6 个百分点。 细分来看,Forest 5 函数结果从 0.7626 升到 0.9816,Megacity 5 函数从 0.36 升到 0.7031;500 维高维场景下收益递减但仍优于初版。该版在 27 个算法横向对比中列第 12 位,特征是在全局探索与局部挖掘间靠「推测型 / 实践型思考者」分工维持平衡,收敛快但精度不算顶尖。 外汇与贵金属行情受流动性突变影响,这类优化器直接套用存在过拟合高风险;MT5 上验证时建议先用历史 tick 重跑上述 1 万次 func runs,确认随机布尔扰动逻辑后再考虑接入实盘信号过滤。
◍ 低分优化器的实测排序与随机游走基线
上面这张横评表继续列了第 28 到 45 号算法以及 RW 随机游走的指标表现,编号越靠后整体得分越低。AEFA 人工电场算法拿了 4.459 分(百分制 49.55),是这一区段里唯一仍接近 50 分的;往下 ABHA、ACMO、ADAMm 依次落在 44~46 分区间,衰减并不陡。 到了 36 号 ATAm 人工部落算法 M,总分掉到 3.832(42.58),再往后的 ASHA、ASBO 都已不足 41 分。MEC 思维进化计算 3.470 分(38.55),IWO 入侵性杂草优化 3.403 分(37.81),Micro-AIS 微型人工免疫系统 3.379 分(37.54),COAm、SDOm、NMm、BBBC 则依次滑到 35 分上下。 RW 随机游走作为无脑基线,三项主指标仅 0.48754 / 0.32159 / 0.25781,总分 2.348(26.09),比最差的 BBBC 还低近 9 分。做 MT5 EA 参数寻优时,若自写算法跑分掉到 RW 的两倍以下,基本可判定该优化器在样本外已无实用价值,外汇与贵金属品种波动结构复杂,这类低分寻优结果实盘失效概率偏高,建议直接换算法或缩参数域。
「把这条线请下神坛」
辩证算法(DA)把种群拆成推测型与实践型两类思考者,前者广撒网做全局探索,后者在优区深耕做局部利用,靠这种对立统一来平衡搜索与开发。它只有两个外部参数(不含种群规模),实现简单且跑得相当快,在小问题和大问题上都给出了不偏科的均匀表现。 测试台用 0–100 的评分梯度衡量算法,100 为理论上限;DA 在对比表里颜色渐变平稳,没显出明显优于或劣于同类方法的偏向。附带的 DA.zip(158.45 KB)里含 Test_AO_DA.mq5 等脚本,在 MT5 里直接跑就能复现那张直方图。 这套方法毕竟带着作者为贴近辩证逻辑而故意掺入的随机扰动,结果分散是硬伤,别把它当确定性圣杯。外汇与贵金属市场高波动、高杠杆,任何优化器都只是辅助筛参工具,真要上实盘前请在策略测试器里用自己的品种数据再验一遍。