辩证搜索(DA)(基础篇)
辩证搜索在 MT5 里的落地雏形
辩证搜索(DA)是一套把候选参数空间反复做对立面验证的算法思路,在 MetaTrader 5 中可作为交易系统优化器之外的补充筛选层。它不依赖单次回测的漂亮曲线,而是让一组参数在「盈利假设」与「反向证伪」之间来回碰撞。 原文作者 Andrey Dik 在 2025 年 12 月 3 日 10:01 发布了该主题,截至统计时点获得 392 次查看、0 条评论,说明这类偏底层算法内容在零售交易者中曝光有限但具备深挖价值。 该小节仅给出「概述 / 算法的实现 / 测试结果」三段骨架,尚未展开代码与回测数值;若你要验证可行性,可先在 MT5 策略测试器外用自定义 DLL 跑一轮参数对立搜索,再比对原生遗传优化结果。外汇与贵金属品种波动跳空频繁,任何算法筛选都只是降低过拟合概率,不消除实盘高风险。
◍ 把矛盾对立写进寻优循环
辩证唯物主义讲发展来自对立力量的冲突,量变堆到临界点就跳变到质变,再经否定之否定形成保留旧有优点的综合。2009 年 Serdar Kadioglu 与 Meinolf Sellmann 提出的辩证算法(DA)把这套哲学搬进了约束优化:任一解视为正题,内含与其反题互动改进的潜力。 算法实现里,思辨型思考者用大步长在解空间撒网,实践型思考者贴着当前解做局部搜。群体被切成两类:推测型 k1 让质量相近但空间距离远的解互相作用;实践型 p-k1 让质量差但空间近的解互相作用。这种划分就是对立统一在寻优里的落地。 一轮搜索分三步:先理解(评估并排解),再辩证互动(给解找对立面),最后合成出新解。解一出来就引发新矛盾,逼出下一轮,形成连续改进。你在 MT5 里跑自定义优化器时,可对照这个结构检查自己的种群划分是否真的制造了对立,而不是同质扰动。
「辩证优化算法的代码骨架」
辩证算法(DA)把 50 个代理拆成三组:1 个最优思考者、k1=3 个推测型思考者、剩下 46 个实践型思考者。位置更新统一走 X(i)=X(i)+μ⊙(Xanti(i)-X(i)),μ 来自均匀分布或正态分布,逐元素乘保证两类思考者分别负责广域探索与局部细化。 与差分进化(DE)相比,DA 的解更新方程形式接近,但向量选取不是随机差分,而是按欧氏距离与解质量确定性地挑对立面。实测中两类思考者都用均匀分布时全局搜索效率更高,尽管计算距离让复杂度略升。 下面这段 MQL5 类声明给出了参数默认值与初始化逻辑。popSize=50、k1=3、k2=10 直接写死在构造函数,运行时也能通过 SetParams 改。开 MT5 新建 EA 把类挂上,先跑默认参数看收敛曲线,再调 k1 观察推测组比例对结果的影响。 代码逐行拆解:
- class C_AO_DA : public C_AO:从基类 C_AO 继承,复用通用优化框架。
- ao_name="DA" / ao_desc="Dialectical Algorithm":算法标识与描述,用于日志区分。
- popSize=50:种群规模,即参与优化的代理总数。
- k1=3:推测型思考者数量,决定自由探索的精英个数。
- k2=10:每个推测型思考者交互的最近邻居数。
- ArrayResize(params,3):参数数组扩到 3 项,存 popSize/k1/k2。
- SetParams():把 params 里的值回写到成员变量,支持运行中动态调参。
- Init() 入参:rangeMinP 最小边界、rangeMaxP 最大边界、rangeStepP 步长,供后续随机布点用。
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span> <span class="comment">class=class="str">"cmt">// Class implementing the dialectical optimization algorithm</span> <span class="keyword">class</span> C_AO_DA : <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_DA() { } C_AO_DA() { ao_name = <span class="class="type">class="kw">string">"DA"</span>; ao_desc = <span class="class="type">class="kw">string">"Dialectical Algorithm"</span>; ao_link = <span class="class="type">class="kw">string">"[MQL5官方文档] popSize = <span class="number">class="num">50</span>; <span class="comment">class=class="str">"cmt">// population size</span> k1 = <span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">// speculative thinkers</span> k2 = <span class="number">class="num">10</span>; <span class="comment">class=class="str">"cmt">// neighbours</span> ArrayResize(<span class="keyword">params</span>, <span class="number">class="num">3</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">"k1"</span>; <span class="keyword">params</span> [<span class="number">class="num">1</span>].val = k1; <span class="keyword">params</span> [<span class="number">class="num">2</span>].name = <span class="class="type">class="kw">string">"k2"</span>; <span class="keyword">params</span> [<span class="number">class="num">2</span>].val = k2; } <span class="comment">class=class="str">"cmt">// Setting algorithm parameters</span> <span class="keyword">class="type">void</span> SetParams() { popSize = (<span class="keyword">class="type">int</span>)<span class="keyword">params</span> [<span class="number">class="num">0</span>].val; k1 = (<span class="keyword">class="type">int</span>)<span class="keyword">params</span> [<span class="number">class="num">1</span>].val; k2 = (<span class="keyword">class="type">int</span>)<span class="keyword">params</span> [<span class="number">class="num">2</span>].val; } <span class="keyword">class="type">bool</span> Init(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &rangeMinP [], <span class="comment">class=class="str">"cmt">// minimum search range</span> <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &rangeMaxP [], <span class="comment">class=class="str">"cmt">// maximum search range</span> <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &rangeStepP [], <span class="comment">class=class="str">"cmt">// search step</span>
距离计算与初始化落点
套利者优化器的核心里,两个解向量之间的远近用欧氏距离衡量。代码里的 EuclideanDistance 接收两个 double 数组和维度 dim,逐维求差的平方并累加,最后开根号返回。 具体拆解:sum 初值为 0,diff 初值 0.0;for 循环从 i=0 跑到 dim-1,每轮 diff = vec1[i] - vec2[i],sum += diff*diff;循环结束 return MathSqrt(sum)。这一步没有任何魔法,就是标准 L2 范数。 Init 函数负责把搜索范围接进来:rangeMinP、rangeMaxP、rangeStepP 分别是最小范围、最大范围、搜索步长,epochsP 默认 0 表示迭代轮数。内部先调 StandardInit 做范围合法性校验,失败直接返回 false,通过才返回 true。 在 MT5 里把这段贴进 EA 或指标工程,改 dim 对应的参数维度,就能验证不同参数向量间的距离变化;外汇与贵金属杠杆高、滑点大,实盘前请用策略测试器跑历史数据确认稳定性。
const class="type">int epochsP = class="num">0); class=class="str">"cmt">// number of epochs class="type">void Moving(); class=class="str">"cmt">// Moving agents in the search space class="type">void Revision(); class=class="str">"cmt">// Review and update the best solutions found class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">int k1; class=class="str">"cmt">// number of speculative thinkers class="type">int k2; class=class="str">"cmt">// number of neighbors to analyze class="kw">private: class=class="str">"cmt">//------------------------------------------------------------------- class=class="str">"cmt">// Calculate the Euclidean distance between two vectors class="type">class="kw">double EuclideanDistance(const class="type">class="kw">double &vec1 [], const class="type">class="kw">double &vec2 [], const class="type">int dim) { class="type">class="kw">double sum = class="num">0; class="type">class="kw">double diff = class="num">0.0; for (class="type">int i = class="num">0; i < dim; i++) { diff = vec1 [i] - vec2 [i]; sum += diff * diff; } class="kw">return MathSqrt(sum); }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_DA::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">// search step 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">//---------------------------------------------------------------------------- class="kw">return true; } class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————