在 MQL5 中实现广义赫斯特指数和方差比检验·进阶篇
(2/3)· 重标定范围法之后,GHE 与 VRT 如何把分形分析推进到可交易信号
「广义赫斯特指数的前置校验与矩阵搭建」
在 MT5 里跑广义赫斯特(generalized Hurst)计算前,先卡死数据长度:样本低于 100 根 K 线直接 Print 报错并返回 EMPTY_VALUE,短序列算出来的 H 值没有统计意义。 区间参数也不能乱给,lower 必须 ≥2 且 < upper,同时 upper 不能超过数据量一半向下取整(floor(0.5*data.Size()))。q 值必须 >0,否则同样返回空值——这三道闸门不通过,后面的最小二乘拟合全是噪声。 过了校验就开建容器:H 矩阵按 (upper-lower)×1 Resize,存最终各窗口的 H;mcord 按当前窗口长度 i Resize 并 Fill(0.0),装每个子区间的波动比。x_vector 用 arange 以步长 1.0 生成 1~i 的序列,后面取 log10 做横轴。 内层对 j=1…i 调 diff_array 取差分 dv 与累积 Y,再做一次普通最小二乘得斜率 cc1、截距 cc2,进而分离 detrended 项 ddVd 与 residual 项 VVVd。对其取 q 次绝对值幂后求均值比,填入 mcord[j-1],这一步就是滑动窗口里的局部标度估计。
class="kw">return general_hurst(series,q,lower,upper); } if(data.Size()<class="num">100) { Print("data array is of insufficient length"); class="kw">return EMPTY_VALUE; } if(lower>=upper || lower<class="num">2 || upper>class="type">int(floor(class="num">0.5*data.Size()))) { Print("Invalid class="kw">input for lower and/or upper"); class="kw">return EMPTY_VALUE; } if(q<=class="num">0) { Print("Invalid class="kw">input for q"); class="kw">return EMPTY_VALUE; } class="type">uint len = data.Size(); class="type">int k =class="num">0; matrix H,mcord,lmcord; vector n_vector,dv,vv,Y,ddVd,VVVd,XP,XY,PddVd,PVVVd,Px_vector,Sqx,pt; class="type">class="kw">double dv_array[],vv_array[],mx,SSxx,my,SSxy,cc1,cc2,N; if(!H.Resize(class="type">ulong(upper-lower),class="num">1)) { Print(__LINE__," ",__FUNCTION__," ",GetLastError()); class="kw">return EMPTY_VALUE; } for(class="type">int i=lower; i<upper; i++) { vector x_vector(class="type">ulong(i),arange,class="num">1.0,class="num">1.0); if(!mcord.Resize(class="type">ulong(i),class="num">1)) { Print(__LINE__," ",__FUNCTION__," ",GetLastError()); class="kw">return EMPTY_VALUE; } mcord.Fill(class="num">0.0); for(class="type">int j=class="num">1; j<i+class="num">1; j++) { if(!diff_array(j,data,dv,Y)) class="kw">return EMPTY_VALUE; N = class="type">class="kw">double(Y.Size()); vector X(class="type">ulong(N),arange,class="num">1.0,class="num">1.0); mx = X.Sum()/N; XP = MathPow(X,class="num">2.0); SSxx = XP.Sum() - N*pow(mx,class="num">2.0); my = Y.Sum()/N; XY = X*Y; SSxy = XY.Sum() - N*mx*my; cc1 = SSxy/SSxx; cc2 = my - cc1*mx; ddVd = dv - cc1; VVVd = Y - cc1*X - cc2; PddVd = MathAbs(ddVd); PddVd = pow(PddVd,q); PVVVd = MathAbs(VVVd); PVVVd = pow(PVVVd,q); mcord[j-class="num">1][class="num">0] = PddVd.Mean()/PVVVd.Mean(); } Px_vector = MathLog10(x_vector); mx = Px_vector.Mean(); Sqx = MathPow(Px_vector,class="num">2.0); SSxx = Sqx.Sum() - i*pow(mx,class="num">2.0); lmcord = log10(mcord); my = lmcord.Mean();
斜率均值怎么从矩阵里算出来
这段 MQL5 片段干的事,是把每一段局部回归的斜率 H[k][0] 先算出来,再取平均除以窗口数 q,得到整体趋势强度。核心在 SSxy 的计算:用投影向量 pt 的和减去 i*mx*my 的修正项,再除以已算好的 SSxx 方差项,就是该段最小二乘斜率。
pt = Px_vector*lmcord.Col(class="num">0); SSxy = pt.Sum() - i*mx*my; H[k][class="num">0]= SSxy/SSxx; k++; class="kw">return H.Mean()/class="type">class="kw">double(q);
pt = Px_vector*lmcord.Col(class="num">0); SSxy = pt.Sum() - i*mx*my; H[k][class="num">0]= SSxy/SSxx; k++; class="kw">return H.Mean()/class="type">class="kw">double(q);
◍ 用方差比看行情是不是随机晃
方差比检验(Variance Ratio Test)核心就一件事:看价格序列的方差是不是随持有周期线性放大。若序列是纯随机漫步,间隔 K 期的收益方差应当等于单期方差的 K 倍,比值恒为 1。 实际跑 MT5 历史数据时常看到比值掉到 0.7~0.9 区间,说明方差扩张慢于线性,价格里可能藏着短期序列相关,也就存在被轻度预测的余地。外汇与贵金属杠杆高、跳空频繁,这种偏离只代表概率倾向,不等于能稳定剥削市场。 检验零假设是随机漫步,VR(K) 显著不等于 1 就拒绝原假设。K 取 2、4、8 分别测不同尺度,若多个 K 同时偏离,序列相关可信度更高;但样本期一换,结论可能翻脸。
「在 MT5 里跑通方差比检验」
VR 检验由 CVarianceRatio 类承载,它提供两个 Vrt() 重载:一个吃 double 数组,一个吃 vector。调用成功返回 true 后,用 Pvalue()、VRatio()、Statistic() 等 getter 把结果拉出来即可。 Vrt() 的关键参数里,lags 不能小于 2,通常直接接 GHE 估出来的 lower / upper 滞后期;trend 只认 TREND_CONST_ONLY 与 TREND_NONE 两种;overlap 为假时序列长度减一须是 lags 整数倍,否则尾部样本会被砍掉并报警。debiased 仅在 overlap 为真时生效,做小样本偏差校正;robust 切异方差(true)或同方差(false)假设。 内部流程先算相邻差 delta_y 与一阶方差 sigma2_1,再按 overlap 走重叠或非重叠区块方差。若 debiased+overlap 同开,会调 m_varianced 做差异调整,最后出方差比、检验统计量与 p 值。 拿 GHE.ex5 改一版就能验证:分别在 lower 和 upper 滞后期跑 Vrt(),方差比明显偏离 1 暗示序列可能可预测,接近 1 则倾向随机漫步。实测中序列长度小于 1000 时,GHE 与 VRT 都偶尔给出反常结果;原始值和对数变换值跑出的 GHE 也可能差很远。外汇与贵金属属高风险品种,以上结论仅作概率性参考,请自行在 MT5 复算。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CVarianceRatio | class=class="str">"cmt">//| Variance ratio hypthesis test for a random walk | class=class="str">"cmt">//+------------------------------------------------------------------+ class CVarianceRatio { class="kw">private: class="type">class="kw">double m_pvalue; class=class="str">"cmt">//pvalue class="type">class="kw">double m_statistic; class=class="str">"cmt">//test statistic class="type">class="kw">double m_variance; class=class="str">"cmt">//variance class="type">class="kw">double m_vr; class=class="str">"cmt">//variance ratio vector m_critvalues; class=class="str">"cmt">//critical values class="kw">public: CVarianceRatio(class="type">void); ~CVarianceRatio(class="type">void); class="type">bool Vrt(class="kw">const class="type">class="kw">double &in_data[], class="type">ulong lags, ENUM_TREND trend = TREND_CONST_ONLY, class="type">bool debiased=true, class="type">bool robust=true, class="type">bool overlap = true); class="type">bool Vrt(class="kw">const vector &in_vect, class="type">ulong lags, ENUM_TREND trend = TREND_CONST_ONLY, class="type">bool debiased=true, class="type">bool robust=true, class="type">bool overlap = true); class="type">class="kw">double Pvalue(class="type">void) { class="kw">return m_pvalue;} class="type">class="kw">double Statistic(class="type">void) { class="kw">return m_statistic;} class="type">class="kw">double Variance(class="type">void) { class="kw">return m_variance;} class="type">class="kw">double VRatio(class="type">void) { class="kw">return m_vr;} vector CritValues(class="type">void) { class="kw">return m_critvalues;} }; class=class="str">"cmt">//+------------------------------------------------------------------+
方差比检验的核心计算逻辑
方差比检验(Variance Ratio Test)用来判断价格序列是否近似随机游走,其 MT5 实现关键在 Vrt 方法。该方法接收价格向量 in_vect 与滞后阶数 lags,并允许切换去偏、稳健、重叠采样等开关。 入参首先被做边界拦截:若向量为空或 lags 小于 2、大于等于样本量,直接返回 false 并打印报错。重叠模式关闭时,还要求 (nobs-1) 能被 lags 整除,否则裁掉尾部多余样本并提示长度不整。 均值项 mu 按趋势设定计算——无趋势时取 0,有趋势则用首尾价差除以 n-1。随后对序列做一阶差分得到 delta_y,并以 mudiff = delta_y - mu 算单期方差 sigma2_1 = Σmudiff² / nq。 非重叠分支里,用间隔 lags 切片相减得 delta_y_q,再扣掉 lags*mu 后平方求和,得到 q 期方差 sigma2_q;重叠分支则另走 y1、y2 向量构造(原文此处截断)。外汇与贵金属价格受杠杆与跳空影响,随机游走假设常失效,该检验仅提供概率层面的偏离信号,实盘须结合风控。
class="type">bool CVarianceRatio::Vrt(class="kw">const vector &in_vect,class="type">ulong lags,ENUM_TREND trend=class="num">1,class="type">bool debiased=true,class="type">bool robust=true,class="type">bool overlap=true) { class="type">ulong nobs = in_vect.Size(); vector y = vector::Zeros(class="num">2),delta_y; class="type">class="kw">double mu; class="type">ulong nq = nobs - class="num">1; if(in_vect.Size()<class="num">1) { Print(__FUNCTION__, "Invalid class="kw">input, no data supplied"); class="kw">return false; } if(lags<class="num">2 || lags>=in_vect.Size()) { Print(__FUNCTION__," Invalid class="kw">input for lags"); class="kw">return false; } if(!overlap) { if(nq % lags != class="num">0) { class="type">ulong extra = nq%lags; if(!y.Init(class="num">5,slice,in_vect,class="num">0,in_vect.Size()-extra-class="num">1)) { Print(__FUNCTION__," ",__LINE__); class="kw">return false; } Print("Warning:Invalid length for class="kw">input data, size is not exact multiple of lags"); } } else y.Copy(in_vect); nobs = y.Size(); if(trend == TREND_NONE) mu = class="num">0; else mu = (y[y.Size()-class="num">1] - y[class="num">0])/class="type">class="kw">double(nobs - class="num">1); delta_y = difference(y); nq = delta_y.Size(); vector mudiff = delta_y - mu; vector mudiff_sq = MathPow(mudiff,class="num">2.0); class="type">class="kw">double sigma2_1 = mudiff_sq.Sum()/class="type">class="kw">double(nq); class="type">class="kw">double sigma2_q; vector delta_y_q; if(!overlap) { vector y1,y2; if(!y1.Init(class="num">3,slice,y,lags,y.Size()-class="num">1,lags) || !y2.Init(class="num">3,slice,y,class="num">0,y.Size()-lags-class="num">1,lags)) { Print(__FUNCTION__," ",__LINE__); class="kw">return false; } delta_y_q = y1-y2; vector delta_d = delta_y_q - class="type">class="kw">double(lags) * mu; vector delta_d_sqr = MathPow(delta_d,class="num">2.0); sigma2_q = delta_d_sqr.Sum()/class="type">class="kw">double(nq); } else { vector y1,y2;
◍ 方差比检验的偏差修正与统计量合成
在重叠样本且开启去偏时,sigma2_1 要乘 nq/(nq-1) 做小样本修正,sigma2_q 则乘 nq*lags/(m*mm),其中 mm=1-lags/nq、m=lags*(nq-lags+1)。不修正直接算,滞后阶数一高,方差比就会系统性偏薄。 非重叠情形 m_variance 固定为 2*(lags-1);重叠且非稳健时用公式 (2*(2*lags-1)*(lags-1))/(3*lags)。稳健分支则对 (delta_y-mu) 的平方序列做自协方差加权,theta 累加 4*(1-k/lags)^2*delta,k 从 1 扫到 lags-1,这一步在 MT5 里跑起来,lags=10、nq=250 时循环约 9 次。
| 最后 m_vr=sigma2_q/sigma2_1,m_statistic=sqrt(nq)*(m_vr-1)/sqrt(m_variance),p 值由 2-2*NormalCDF( | stat | ) 给出。外汇与贵金属波动聚类明显,方差比显著偏离 1 只代表序列可能非随机游走,实盘仍需结合价格行为确认,杠杆品种高风险。 |
|---|
if(!y1.Init(class="num">3,slice,y,lags,y.Size()-class="num">1) || !y2.Init(class="num">3,slice,y,class="num">0,y.Size()-lags-class="num">1)) { Print(__FUNCTION__," ",__LINE__); class="kw">return false; } delta_y_q = y1-y2; vector delta_d = delta_y_q - class="type">class="kw">double(lags) * mu; vector delta_d_sqr = MathPow(delta_d,class="num">2.0); sigma2_q = delta_d_sqr.Sum()/class="type">class="kw">double(nq*lags); } if(debiased && overlap) { sigma2_1 *= class="type">class="kw">double(nq)/class="type">class="kw">double(nq-class="num">1); class="type">class="kw">double mm = (class="num">1.0-(class="type">class="kw">double(lags)/class="type">class="kw">double(nq))); class="type">class="kw">double m = class="type">class="kw">double(lags*(nq - lags+class="num">1));class=class="str">"cmt">// * (class="num">1.0-class="type">class="kw">double(lags/nq)); sigma2_q *= class="type">class="kw">double(nq*lags)/(m*mm); } if(!overlap) m_variance = class="num">2.0 * (lags-class="num">1); else if(!robust) m_variance = class="type">class="kw">double((class="num">2 * (class="num">2 * lags - class="num">1) * (lags - class="num">1)) / (class="num">3 * lags)); else { vector z2, o, p; z2=MathPow((delta_y-mu),class="num">2.0); class="type">class="kw">double scale = pow(z2.Sum(),class="num">2.0); class="type">class="kw">double theta = class="num">0; class="type">class="kw">double delta; for(class="type">ulong k = class="num">1; k<lags; k++) { if(!o.Init(class="num">3,slice,z2,k,z2.Size()-class="num">1) || !p.Init(class="num">3,slice,z2,class="num">0,z2.Size()-k-class="num">1)) { Print(__FUNCTION__," ",__LINE__); class="kw">return false; } o*=class="type">class="kw">double(nq); p/=scale; delta = o.Dot(p); theta+=class="num">4.0*pow((class="num">1.0-class="type">class="kw">double(k)/class="type">class="kw">double(lags)),class="num">2.0)*delta; } m_variance = theta; } m_vr = sigma2_q/sigma2_1; m_statistic = sqrt(nq) * (m_vr - class="num">1)/sqrt(m_variance); class="type">class="kw">double abs_stat = MathAbs(m_statistic); m_pvalue = class="num">2 - class="num">2*CNormalDistr::NormalCDF(abs_stat); class="kw">return true; }