在 MQL5 中实现广义赫斯特指数和方差比检验·进阶篇
📐

在 MQL5 中实现广义赫斯特指数和方差比检验·进阶篇

(2/3)· 重标定范围法之后,GHE 与 VRT 如何把分形分析推进到可交易信号

实战向 第 2/3 篇
不少交易者把赫斯特指数算出来就直接当趋势强度用,却忽略 q 阶矩切换带来的性质误判。只用 R/S 法容易在均值回复与长记忆之间给出模糊结论,外汇品种筛选因此踩坑。本篇换一套更细的尺子,先看清序列再谈进出场。

「广义赫斯特指数的前置校验与矩阵搭建」

在 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],这一步就是滑动窗口里的局部标度估计。

MQL5 / C++
  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 方差项,就是该段最小二乘斜率。

MQL5 / C++
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);
逐行拆:第一行把设计矩阵列 0 与设计向量相乘得投影;第二行算交叉离差和;第三行存斜率;第四行游标推进;末行返回平均斜率归一值。 在 MT5 里把 q 设为 20、局部长度 i 设为 5 跑一遍,能直接看到外汇 1H 图上均值斜率在 0.0001~0.0003 区间震荡,贵金属 XAUUSD 同参数下常放大 3~5 倍。杠杆品种波动高风险,该值仅作趋势倾向参考,不代表方向确定性。

MQL5 / C++
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 复算。

MQL5 / C++
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 向量构造(原文此处截断)。外汇与贵金属价格受杠杆与跳空影响,随机游走假设常失效,该检验仅提供概率层面的偏离信号,实盘须结合风控。

MQL5 / C++
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 只代表序列可能非随机游走,实盘仍需结合价格行为确认,杠杆品种高风险。
MQL5 / C++
  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;
  }
把品种初筛交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 GHE 与方差比的双栏读数,你只需复核信号逻辑。

常见问题

q=1 描述绝对偏差的缩放特性,q=2 对长程依赖性最敏感;变换 q 可让算法突出序列不同统计特征,分类更细。
VRT 提供统计显著性层面的佐证,但应与 GHE 联合使用;单靠检验在样本不足时可能给出偏误结论。
可以,小布盯盘的 AIGC 模块已内置两类读数,并在品种页标注倾向均值回复的概率,省去手动编译指标。
gen_hurst() 同时提供向量与数组重载,向量形式便于接 Matrix 工具链,数组形式适合直接从缓冲区喂数据。
点差与隔夜成本会侵蚀回复利润,且外汇贵金属属高风险,信号仅表示概率倾向而非确定性回归。