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