确定性振荡搜索(DOS)(基础篇)
「确定性振荡搜索到底是什么」
确定性振荡搜索(DOS)是一套跑在 MetaTrader 5 上的算法思路,核心是在价格序列里找周期性、可重复的振荡结构,而不是去拟合某条均线或通道。它把行情看成由若干确定成分叠加而成,试图把噪声剥掉后定位那些稳定来回摆动的区间。 这套方法的落地形态偏研究向:作者在 2026 年 6 月 2 日公开的版本里,原文标注了 182 次浏览、0 条评论,说明它还处在早期被少数人翻看的阶段,没经过大量社区拷打。你在 MT5 里搜同名指标或 EA 时,可以用这个发布时间和作者名交叉确认是不是同一份实现。 真正要动手验证,得看两件事:算法实现部分怎么定义“振荡被找到”,以及测试结果里用的是哪段品种、哪段周期。外汇和贵金属波动受杠杆与跳空影响大,这类周期搜索在实盘里失效的概率不低,先用策略测试器跑历史再谈。
◍ 确定性振荡搜索为什么值得交易者看一眼
传统元启发式优化大多靠随机数驱动,换一组种子结果就漂移,回测里跑出来的参数放到实盘往往对不上。对外汇或贵金属这类高杠杆、高波动品种,不可复现的优化结论风险很大,你可能把偶然当必然。 DOS(Deterministic Oscillatory Search)由 Archana 在 2017 年提出,核心是用确定性初始分布加粒子振荡运动替代随机扰动。相同初始条件下每次运行结果完全一致,这点对 MT5 上做 EA 参数寻优的人很实用——至少能稳定复现。 它引入“适应度斜率”状态:正向、负向、未知。粒子据此判断移动是否改善解,传统梯度法卡在局部劣点就停,DOS 靠群体机制把粒子拉向已知全局最优继续搜。 下文会拆它的数学基础和实现,并给测试问题验证效率。你要验真伪,直接开 MT5 用固定种子跑一遍自己的优化任务对比即可。
DOS 算法的粒子运动与反弹逻辑
DOS 不走随机游走,而是用确定性规则铺粒子:初始位置由公式算出来均匀覆盖搜索空间,每个粒子带初速和斜率状态(正向+1 / 负向-1 / 未知0)。它靠「当前位置适应度比上一代好还是差」来判断斜率,相当于用最省事的方式知道有没有在朝优解走。 关键在反弹:粒子往前挪,一旦发现适应度变差(斜率从+1翻到-1),不回头也不停,而是掉头、速度减半继续走。这种振荡让它在极值附近锯齿形逼近,像网球撞墙后泄力回弹。若四面都是下坡、陷进局部小山丘,就切「群集」——速度叠加上指向全体已知最优解的向量,向全局最优靠。 代码里的 S_DOS_Velocity 结构体把斜率和分维速度打包,Init 清零、IsZero 用 1e-10 精度判死速;C_AO_DOS 的 Moving 先存旧适应度 f 再按坐标+速度更新并夹在 rangeMin/rangeMax 内,Revision 刷新全局最优后调粒子调整,ProcessParticleMovement 按上面三条斜率规则改向或减半速或群集。 实操上,你在 MT5 里把 popSize 设 30、movementFactor 设 0.5 跑这个函数,能直接看到粒子从确定性起点出发、振荡收敛的过程;外汇或贵金属参数优化属高风险,回测优解实盘可能漂移,仅作算法验证用。
class=class="str">"cmt">// Structure for storing particle velocity class="kw">struct S_DOS_Velocity { class="type">int slope; class=class="str">"cmt">// Particle slope(-class="num">1: negative, class="num">0: unknown, class="num">1: positive) class="type">class="kw">double v []; class=class="str">"cmt">// Velocity components for each dimension class="type">void Init(class="type">int dims) { slope = class="num">0; ArrayResize(v, dims); ArrayInitialize(v, class="num">0.0); class=class="str">"cmt">// Quick initialization of the entire array to zeros } class=class="str">"cmt">// Check for zero velocity class="type">bool IsZero(class="type">class="kw">double epsilon = class="num">1e-10) { for (class="type">int i = class="num">0; i < ArraySize(v); i++) if (MathAbs(v [i]) > epsilon) class="kw">return class="kw">false; class="kw">return true; } }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class C_AO_DOS : class="kw">public C_AO { class="kw">public: class=class="str">"cmt">//-------------------------------------------------------------------- ~C_AO_DOS() { } C_AO_DOS() { ao_name = "DOS"; ao_desc = "Deterministic Oscillatory Search"; ao_link = "[MQL5官方文档] class=class="str">"cmt">// Set class="kw">default parameters
「粒子群参数落地的边界约束」
在 MT5 里跑自定义优化器,种群规模与移动因子这两个量直接决定收敛速度和过拟合概率。代码里把 popSize 默认设为 30、movementFactor 设为 0.95,意味着每代保留 30 个候选解,且向历史最优的偏移保留 95% 权重。 SetParams 做了硬下限保护:popSize 用 MathMax(5, val) 兜住,少于 5 个粒子时搜索效率会塌;movementFactor 被夹在 0.1 到 1.0 之间,超出这个区间要么抖动失控要么早熟收敛。 Init 里 ArrayResize(velocities, popSize) 按种群数开速度数组,再逐粒子 Init(coords) 初始化各维速度,最后 InitializeParticles 用确定性方式铺初始位置——这套顺序不能乱,否则回测时初始种群会漂移。外汇与贵金属杠杆高,参数边界设错可能在极端行情放大回撤,建议先开 MT5 策略测试器单步跟一遍。
popSize = class="num">30; class=class="str">"cmt">// population size movementFactor = class="num">0.95; class=class="str">"cmt">// movement factor towards the best solution class=class="str">"cmt">// Create and initialize the parameters array ArrayResize(params, class="num">2); params [class="num">0].name = "Population Size"; params [class="num">0].val = popSize; params [class="num">1].name = "Movement Factor"; params [class="num">1].val = movementFactor; } class="type">void SetParams() { class=class="str">"cmt">// Set parameter values with validation popSize = (class="type">int)MathMax(class="num">5, params [class="num">0].val); class=class="str">"cmt">// Minimum class="num">5 particles for efficiency movementFactor = MathMax(class="num">0.1, MathMin(class="num">1.0, params [class="num">1].val)); class=class="str">"cmt">// Limit from class="num">0.1 to class="num">1.0 } 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="type">void Moving(); class="type">void Revision(); class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">class="kw">double movementFactor; class=class="str">"cmt">// movement factor towards the best solution S_DOS_Velocity velocities []; class=class="str">"cmt">// Array of particle velocity structures class="kw">private: class=class="str">"cmt">//------------------------------------------------------------------- class="type">void InitializeParticles(); class="type">void ProcessParticleMovement(class="type">int particleIndex); }; class="type">bool C_AO_DOS::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=class="str">"cmt">// Standard C_AO initialization if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false; class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">// Allocating memory for arrays ArrayResize(velocities, popSize); class=class="str">"cmt">// Initialize the velocities for each dimension for (class="type">int i = class="num">0; i < popSize; i++) velocities [i].Init(coords); class=class="str">"cmt">// Initialize particle positions deterministically InitializeParticles(); class="kw">return true; }