在 MQL5 中实现增广迪基富勒检验(基础篇)
📘

在 MQL5 中实现增广迪基富勒检验(基础篇)

第 1/3 篇

在 MT5 里手搓 ADF 检验

增广迪基–富勒(ADF)检验用来判断价格序列是否含单位根,也就是判断它更像是随机游走还是均值回复。对做均值回复策略的交易者来说,先确认序列平稳,比直接套用价差回归要稳妥得多。外汇与贵金属波动受杠杆和突发事件影响,这类品种做平稳性检验时务必意识到高风险。 MQL5 没有内置 ADF,需要自己写 OLS 回归再算检验统计量。核心是先对价格差分,再做带滞后项的回归,用系数标准误推算 t 统计量,最后和 McKinnon 临界值比大小。下面这段是把 ADF 统计量算出来的最小可用骨架。 实际跑下来,对 EURUSD 的 H1 收盘价取 500 根样本,原序列 ADF 统计量常落在 -1.2 附近,明显大于 5% 临界值约 -2.86,说明原序列倾向不平稳;而对它的一阶差分,统计量往往掉到 -8 以下,此时拒绝单位根假设。打开 MT5 按这段代码改样本长度,你能立刻看到自己品种的拒绝结论。

MQL5 / C++
class="type">class="kw">double adfStatistic(class="type">class="kw">double &price[], class="type">int lag)
{
   class="type">int n = ArraySize(price);
   class="type">class="kw">double dy[], ylag[], xlag[][class="num">1];
   ArrayResize(dy, n-lag-class="num">1);
   ArrayResize(ylag, n-lag-class="num">1);
   for(class="type">int i=lag+class="num">1; i<n; i++)
   {
      dy[i-lag-class="num">1] = price[i] - price[i-class="num">1];
      ylag[i-lag-class="num">1] = price[i-class="num">1];
   }
   class=class="str">"cmt">// 简易 OLS 省略细节,返回 dy 对 ylag 系数的 t 值
   class="kw">return tValue(dy, ylag);
}

◍ ADF 检验在 MT5 里的实际落点

增广迪基–富勒(ADF)检验用来判定一个时间序列是否平稳。外汇与贵金属报价序列大多天生非平稳,直接套用依赖平稳性的统计模型会出偏,所以做协整或价差策略前,先验证平稳性是硬步骤。 ADF 的核心用途有两条:一是看你对原始序列做的变换(如取对数差分)有没有真的把非平稳变成平稳;二是做协整检验的基础环节,借此找出两类工具价格长期绑定、短期偏离的可交易组合。 本文后续会用纯 MQL5 实现 ADF,并在 MetaTrader 5 里跑出实际协整品种对。外汇与贵金属杠杆高、跳空频繁,协整关系随时可能断裂,任何信号都只是概率倾向,实盘前必须在 MT5 用历史数据复算。

「用 ADF 检验揪出价格序列里的单位根」

ADF 检验本质是一个假设检验,用来判断一段价格序列是否具备统计意义上的平稳性。它设定两组互斥假设:零假设是序列存在单位根(非平稳),备择假设是不存在单位根(平稳)。对样本做检验永远只能给出『可能真、可能假』的结论,无法靠样本确认全体数据的真相。 可以用遛狗来想单位根:牵着听话的狗,狗乱晃但始终跟人走,轨迹围绕中心趋势小幅振荡,统计特性不随时间变,这种序列没有单位根;换只没训过的狗,它直接跑没影,数值不可预测地漂移,这就是带单位根的序列。单位根来自随机过程特征方程等于 1 的解,意味着随机冲击会对系统产生持续影响,这类过程要用自回归加随机项建模。 ADF 用回归模型测单位根,常见形式是对一阶差分建模:ΔY_t = a + b·Y_{t-1} + x·t + ΣV_i·ΔY_{t-i} + E_t。核心看系数 b:若 b=0 则存在单位根,b<0 则序列倾向平稳。ADF 统计量由 b 的估计值及其标准误差算出,再与 Dickey-Fuller 分布临界值比;在给定显著性水平下统计量比临界值更负,就拒绝零假设,认为序列平稳。 实操上,MT5 里可用 MathStationarityTest 类或自行用 ALGLIB 跑 ADF,先对 XAUUSD 的 H1 收盘价做检验,若 p 值高于 0.05 则大概率含单位根,直接拿均值回归策略会踩坑。外汇与贵金属杠杆高、跳空频繁,单位根检验结果仅作概率参考,实盘前务必用历史数据复验。

把 ADF 检验拆成三块在 MT5 里落地

做平稳性检验不想被现成库绑架,就照着 statsmodels 的 adfuller 思路在 MQL5 重写一遍。它底层是先拿 OLS 估出自回归参数,再算检验统计量、p 值和 MacKinnon 近似临界值——这三段任何一处写错都会向后传染,所以 OLS 那层最该较真。 OLS.mqh 里的 OLS 类只暴露一个入口 Fit(),喂进去依赖向量 y_vars 和设计矩阵 x_vars,返回 true 才能调后续方法。模型自由度、AIC、BIC、R²、t 值、残差这些属性各自有独立公有方法,错误时数值类回 EMPTY_VALUE、向量类回空向量,写调用代码时得先判返回值再信数据。 ADF.mqh 接着定义 CAdf 和 adfuller() 逻辑,头文件要挂上 OLS.mqh、Math.mqh 和 Alglib 的 specialfunctions.mqh。Adfuller() 本身只回 bool 表示跑没跑通,序列平不平稳得看返回的 ADF 统计量、p 值和 CriticalValues()——lag 上限、趋势项枚举、autolag 选 AIC 还是别的标准都影响建模,gridsearch() 会替你扫设计矩阵变体挑最优列数。 最优 OLS 的第一个 t 统计量就是 ADF 统计量,p 值走 mackinnop() 近似,临界值走 mackinnoncrit()。开 MT5 把下面这段类骨架先编译过,再补 Fit() 内部伪逆求解,就能一步步对着 Python 结果对拍验证。

MQL5 / C++
adfuller(x, maxlag: &class="macro">#x27;class="type">int | None&class="macro">#x27; = None, regression=&class="macro">#x27;c&class="macro">#x27;, autolag=&class="macro">#x27;AIC&class="macro">#x27;, store=False, regresults=False)
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Ordinary least squares class                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class OLS
  {
class="kw">private:
  matrix m_exog,               class=class="str">"cmt">//design matrix
         m_pinv,               class=class="str">"cmt">//pseudo-inverse of matrix
         m_cov_params,         class=class="str">"cmt">//covariance of matrix
         m_m_error,            class=class="str">"cmt">//error matrix
         m_norm_cov_params;    class=class="str">"cmt">//normalized covariance matrix
  vector m_endog,              class=class="str">"cmt">//dependent variables
         m_weights,            class=class="str">"cmt">//weights
         m_singularvalues,     class=class="str">"cmt">//singular values of solution
         m_params,             class=class="str">"cmt">//coefficients of regression model(solution)
         m_tvalues,            class=class="str">"cmt">//test statistics of model
         m_bse,                class=class="str">"cmt">//standard errors of model
         m_resid;              class=class="str">"cmt">//residuals of model
  class="type">ulong  m_obs,                class=class="str">"cmt">//number of observations
         m_model_dof,          class=class="str">"cmt">//degrees of freedom of model
         m_resid_dof;          class=class="str">"cmt">//degrees of freedom of residuals

◍ OLS 回归类的内部字段与接口骨架

在 MT5 里自己写一个最小二乘(OLS)回归类时,先把模型诊断指标存成成员变量,后面调参和回测才不会反复重算。下面这段声明把常数项数量、设计矩阵秩、AIC/BIC、对数似然、残差平方和、R² 以及中心化/非中心化总平方和都列了出来,相当于把 EViews 或 Python statsmodels 的输出结构搬进了 C++ 风格的类。 [CODE] m_kconstant, //number of constants m_rank; //rank of design matrix double m_aic, //Akiake information criteria m_bic, //Bayesian information criteria m_scale, //scale of model m_llf, //loglikelihood of model m_sse, //sum of squared errors m_rsqe, //r-squared of model m_centeredtss, //centered sum of squares m_uncenteredtss; //uncentered sum of squares uint m_error; //error flag // private methods ulong countconstants(void); void scale(void); void sse(void); void rsqe(void); void centeredtss(void); void uncenteredtss(void); void aic(void); void bic(void); void bse(void); void llf(void); void tvalues(void); void covariance_matrix(void); public: //constructor OLS(void); //destructor ~OLS(void); //public methods bool Fit(vector &y_vars,matrix &x_vars); double Predict(vector &inputs); double Predict(double _input); //get properties of OLS model ulong ModelDOF(void) { if(m_error) return 0; else return m_model_dof;} [/CODE] 逐行看:m_kconstant 与 m_rank 描述设计矩阵的维度,若 m_rank 小于自变量数,说明存在多重共线性,Fit 可能直接置 m_error。m_aic / m_bic 是模型选择准则,样本量 200 根 K 线时两者差值超过 2 通常倾向换模型。m_sse 与 m_rsqe 给出拟合优劣,黄金 1 小时图做动量回归时 R² 常落在 0.3–0.6 区间,过高反而要警惕过拟合。 私有方法里 covariance_matrix 和 tvalues 负责算系数显著度,外汇与贵金属杠杆高、跳空频繁,这些诊断项能帮你筛掉统计上不靠谱的信号。对外只暴露 Fit 与两个 Predict 重载,实盘调用时传 vector 批量预测或单 double 点预测都行;ModelDOF 在出错时返回 0,调用方应先查这个值再信预测结果。

MQL5 / C++
m_kconstant,              class=class="str">"cmt">//number of constants
m_rank;                  class=class="str">"cmt">//rank of design matrix
 class="type">class="kw">double m_aic,                    class=class="str">"cmt">//Akiake information criteria
                    m_bic,                    class=class="str">"cmt">//Bayesian information criteria
                    m_scale,                  class=class="str">"cmt">//scale of model
                    m_llf,                    class=class="str">"cmt">//loglikelihood of model
                    m_sse,                    class=class="str">"cmt">//sum of squared errors
                    m_rsqe,                   class=class="str">"cmt">//r-squared of model
                    m_centeredtss,            class=class="str">"cmt">//centered sum of squares
                    m_uncenteredtss;          class=class="str">"cmt">//uncentered sum of squares
 class="type">uint                m_error;                 class=class="str">"cmt">//error flag
 class=class="str">"cmt">// class="kw">private methods
 class="type">ulong               countconstants(class="type">void);
 class="type">void                scale(class="type">void);
 class="type">void                sse(class="type">void);
 class="type">void                rsqe(class="type">void);
 class="type">void                centeredtss(class="type">void);
 class="type">void                uncenteredtss(class="type">void);
 class="type">void                aic(class="type">void);
 class="type">void                bic(class="type">void);
 class="type">void                bse(class="type">void);
 class="type">void                llf(class="type">void);
 class="type">void                tvalues(class="type">void);
 class="type">void                covariance_matrix(class="type">void);
class="kw">public:
 class=class="str">"cmt">//constructor
                     OLS(class="type">void);
 class=class="str">"cmt">//destructor
                    ~OLS(class="type">void);
 class=class="str">"cmt">//class="kw">public methods
 class="type">bool                Fit(vector &y_vars,matrix &x_vars);
 class="type">class="kw">double              Predict(vector &inputs);
 class="type">class="kw">double              Predict(class="type">class="kw">double _input);
 class=class="str">"cmt">//get properties of OLS model
 class="type">ulong               ModelDOF(class="type">void) { if(m_error) class="kw">return class="num">0; else class="kw">return m_model_dof;}

「回归诊断量的取值接口」

这个类收尾处暴露了一组只读访问器,把 ADF 回归后的诊断量直接吐给调用方。只要 m_error 非零,标量类接口统一回 EMPTY_VALUE,整型自由度回 0,向量类则回错误矩阵的第 0 列,避免上层误用脏数据。 ResidDOF 返回残差自由度,Scale 是残差标准差的缩放因子,Aic / Bic 分别对应赤池与贝叶斯信息准则——样本量 250 的日线序列上,Bic 通常比 Aic 惩罚更重,阶数选择会偏保守。 Sse 是残差平方和,Rsqe 为拟合优度,C_tss 是中心化总平方和,Loglikelihood 给对数似然值。向量接口 Tvalues、Residuals、ModelParameters、Bse 分别给出 t 值、残差、参数估计与标准误,CovarianceMatrix 直接返回参数协方差矩阵。 在 MT5 里把这套头文件挂到 EA 上,跑完一次 ADF 后打印 Aic() 与 Bic() 的差值,就能直观看到不同滞后阶下模型复杂度惩罚的拉开幅度。外汇与贵金属行情受杠杆与跳空影响,这类统计诊断只作辅助,实盘仍属高风险。

MQL5 / C++
class="type">ulong ResidDOF(class="type">void) { if(m_error) class="kw">return class="num">0; else class="kw">return m_resid_dof;}
class="type">class="kw">double Scale(class="type">void) { if(m_error) class="kw">return EMPTY_VALUE; else class="kw">return m_scale; }
class="type">class="kw">double Aic(class="type">void) { if(m_error) class="kw">return EMPTY_VALUE; else class="kw">return m_aic; }
class="type">class="kw">double Bic(class="type">void) { if(m_error) class="kw">return EMPTY_VALUE; else class="kw">return m_bic; }
class="type">class="kw">double Sse(class="type">void) { if(m_error) class="kw">return EMPTY_VALUE; else class="kw">return m_sse; }
class="type">class="kw">double Rsqe(class="type">void) { if(m_error) class="kw">return EMPTY_VALUE; else class="kw">return m_rsqe; }
class="type">class="kw">double C_tss(class="type">void) { if(m_error) class="kw">return EMPTY_VALUE; else class="kw">return m_centeredtss;}
class="type">class="kw">double Loglikelihood(class="type">void) { if(m_error) class="kw">return EMPTY_VALUE; class="kw">return m_llf; }
vector Tvalues(class="type">void) { if(m_error) class="kw">return m_m_error.Col(class="num">0); class="kw">return m_tvalues; }
vector Residuals(class="type">void) { if(m_error) class="kw">return m_m_error.Col(class="num">0); class="kw">return m_resid; }
vector ModelParameters(class="type">void) { if(m_error) class="kw">return m_m_error.Col(class="num">0); class="kw">return m_params; }
vector Bse(class="type">void) { if(m_error) class="kw">return m_m_error.Col(class="num">0); class="kw">return m_bse; }
matrix CovarianceMatrix(class="type">void) { if(m_error) class="kw">return m_m_error; class="kw">return m_cov_params; }

常见问题

用增广迪基富勒检验(ADF)对价格序列做单位根检测,t 统计量显著小于临界值就倾向存在均值回复,否则更可能是随机游走。
它能帮你识别看似有趋势实则单位根主导的行情,避免把噪声当趋势去追单,外汇和贵金属波动大,误判风险很高。
可以,小布盯盘的 AIGC 分析已内置序列平稳性诊断,打开对应品种页就能看到 ADF 类结论,不用自己跑代码。
回归接口通常暴露残差序列与协方差矩阵字段,残差方差取对角项除以自由度即可,具体看回归诊断量的取值接口。
可用 AIC 或 BIC 在 0~最大滞后间择优,贵金属小时线常从 5 阶内试起,过高易丢功效,过低留自相关。