广义赫斯特指数与方差比检验:用分形特征筛出均值回复品种(基础篇)
📐

广义赫斯特指数与方差比检验:用分形特征筛出均值回复品种(基础篇)

(1/3)· 从 R/S 到 GHE,基础篇讲清幂律矩估计与 MQL5 落地的第一步

实战向 第 1/3 篇

多数交易者把赫斯特指数等同于那套老旧的 R/S 重标定区间法,遇到不同时间尺度下波动结构突变就束手无策。广义赫斯特指数把阶矩参数 q 放开,才真正能区分趋势、随机与均值回复三种状态。本篇是三步系列的第一部分,先把 GHE 数学骨架与 MQL5 实现底座搭好,为后面识别外汇品种和写信号指标铺路。

◍ 用 MT5 跑通广义赫斯特与方差比

想在 MT5 里判断一段外汇或贵金属价格是趋势市还是均值回归市,广义赫斯特指数 H 和方差比检验是两条硬指标。H 大于 0.5 倾向趋势延续,小于 0.5 倾向反转,等于 0.5 接近随机游走;方差比偏离 1 越远,序列越不像有效市场。 下面这段 MQL5 脚本把两种算法直接压进一个函数里,加载到 EURUSD 的 H1 周期就能出数。外汇与贵金属杠杆高、跳空频繁,结论只作概率参考,实盘前务必在策略测试器里跑历史样本。 调用时传入价格数组与滞后阶数 q,函数先算重标极差再算方差比,返回结构体里带 H 值和 VR(q)。把 q 从 2 调到 10,能看出不同周期尺度下的记忆性差异——这是单看均线给不了的信息。

「用广义赫斯特指数筛均值回复品种」

原版赫斯特指数常用的 R/S 重标定范围法,对短周期金融序列容易给出偏高的持续性误判。本文换用广义赫斯特指数(GHE),它把分形维的估计从单一标度扩展到多标度,能更细地区分序列是趋势游走、随机游走还是均值回复。 光看 GHE 数值还不够稳,我们引入方差比检验(VRT)做统计交叉验证:若 VRT 在多个滞后阶上显著拒绝随机游走原假设,且 GHE 落于 0 到 0.5 区间,才可把该品种暂列为均值回复候选。外汇与贵金属杠杆高、跳空频繁,这类统计特征只代表历史样本倾向,实盘仍需控仓。 本系列后续会直接给出一套基于 GHE 的进场/出场指标代码,并在最基础的 EA 里跑一遍,让你开 MT5 就能复现候选品种的筛选结果。

广义赫斯特指数到底在算什么

赫斯特指数衡量时间序列的缩放特性:当时间尺度变化时,序列内部模式如何自相似地重复。对平稳序列而言,它相对几何随机游走变化更慢,我们用方差充当扩散率指标,看后续值偏离起点的程度。 公式里的 K 是任意滞后期,可取小于序列长度的任何正整数;最大滞后由你自行决定,所以赫斯特指数本质是在看不同滞后下方差的缩放行为,按幂律定义。 广义赫斯特指数(GHE)把原式中的 2 换成一个可变阶数 q,变成对 q 阶矩的缩放分析。q=1 时描述绝对偏差的缩放;q=2 对研究长程依赖性最关键。实际在 MT5 里,你可以取一段 XAUUSD 的 H1 收盘价,自己算不同 q 下的 GHE,看贵金属的高风险走势是否存在长记忆倾向。

◍ MT5里把GHE写成可调用函数

在 MT5 写广义赫斯特指数(GHE),核心就是封装一个 gen_hurst(),放在 GHE.mqh 里,头部先 include 一个 VectorMatrixTools.mqh,里面提供 arange、slice 这类向量矩阵初始化辅助函数。gen_hurst() 给了两种入口:直接喂 vector,或喂 array;必填整数 q(阶数,不能小于1),可选 lower / upper 限定滞后区间,默认值以外不能超过数据长度一半,lower 自身不能小于2且必须小于 upper。 函数进来先跑参数体检,任一不满足就向“专家”标签页打印错误字符串并返回 EMPTY_VALUE,避免脏数据进计算。主体是一个 lower→upper 的外循环:每个滞后 i 用 arange 造长度为 i 的 x_vector,把 mcord 矩阵扩成 i 行1列;内循环调 diff_array() 算差分存进 dv 和 Y,得到该滞后的方差写回 mcord。外循环末尾更新主 H 矩阵,最后返回 H 矩阵除以 q 的均值——这就是该 q 阶下的 GHE 估计。 验证别只靠眼睛。官方配套 GHE.ex5 是个交互 EA,能生成指定特征的随机序列、实时拖参数看 GHE 输出,还支持先对数变换再算,方便比对预处理有没有用。真实行情噪音重、样本量一变估计就飘,所以要做方差比(VR)检验看显著性,而不是盯着一个数就下结论。外汇和贵金属波动受事件驱动,样本敏感度高,GHE 结论只代表历史序列的概率特征,实盘仍属高风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                 VectorMatrixTools.mqh |
class=class="str">"cmt">//|                 Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                       [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Vector arange initialization                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">template<class="kw">typename T>
class="type">void arange(vector<T> &vec,T value=class="num">0.0,T step=class="num">1.0)
  {
   for(class="type">ulong i=class="num">0; i<vec.Size(); i++,value+=step)
      vec[i]=value;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Vector sliced initialization                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">template<class="kw">typename T>
class="type">void slice(vector<T> &vec,class="kw">const vector<T> &toCopyfrom,class="type">ulong start=class="num">0,class="type">ulong stop=ULONG_MAX, class="type">ulong step=class="num">1)
  {
   start = (start>=toCopyfrom.Size())?toCopyfrom.Size()-class="num">1:start;
   stop  = (stop>=toCopyfrom.Size())?toCopyfrom.Size()-class="num">1:stop;
   step  = (step==class="num">0)?class="num">1:step;
   class="type">ulong numerator = (stop>=start)?stop-start:start-stop;
   class="type">ulong size = (numerator/step)+class="num">1;
   if(!vec.Resize(size))
     {
      Print(__FUNCTION__ " invalid slicing parameters for vector initialization");
      class="kw">return;
     }
   if(stop>start)
     {
      for(class="type">ulong i =start, k = class="num">0; i<toCopyfrom.Size() && k<vec.Size() && i<=stop; i+=step, k++)
         vec[k] = toCopyfrom[i];
     }
   else
     {

「切片赋值与 OLS 设计矩阵的底层拼装」

把数组按起止位置和步长灌进 vector,核心在 assign 模板。它先夹紧 start 和 stop 不越界,step 为 0 时强制置 1,再用 numerator/step+1 算目标长度;若和原 vector 尺寸不符就 Resize,失败直接 Print 报错返回。 正向切片走 i+=step 从 start 到 stop,反向则把 i 转成 long 做 i-=step,从 start 递减到 stop,两种分支都靠 k 同步写入 vec[k],因此 vec 里元素的顺序严格跟随遍历方向。 rangetrend 用两列拼 matrix:col1 是 arange 生成的等差列(默认从 0.0 步长 1.0),col2 是全 1 列,矩阵固定 Resize 成 r×2。这个结构正好是带截距项的一元回归输入骨架。 olsdmatrix 进一步用 assign 从外部数组切出 col0 作为第一 explanatory 列,行数 r 由切片结果决定;后面只需再补一列常数就能交給 OLS 求解。外汇与贵金属行情受杠杆与跳空影响,回测这类矩阵拟合结果仅代表历史样本关系,实盘胜率可能明显衰减,务必小仓位验证。

MQL5 / C++
class="kw">template<class="kw">typename T>
class="type">void assign(vector<T> &vec,class="kw">const T &toCopyfrom[],class="type">ulong start=class="num">0,class="type">ulong stop=ULONG_MAX, class="type">ulong step=class="num">1)
  {
   start = (start>=toCopyfrom.Size())?toCopyfrom.Size()-class="num">1:start;
   stop  = (stop>=toCopyfrom.Size())?toCopyfrom.Size()-class="num">1:stop;
   step  = (step==class="num">0)?class="num">1:step;
   class="type">ulong numerator = (stop>=start)?stop-start:start-stop;
   class="type">ulong size = (numerator/step)+class="num">1;
   if(size != vec.Size() &&  !vec.Resize(size))
     {
      Print(__FUNCTION__ " invalid slicing parameters for vector initialization");
      class="kw">return;
     }
   if(stop>start)
     {
      for(class="type">ulong i =start, k = class="num">0; i<class="type">ulong(toCopyfrom.Size()) && k<vec.Size() && i<=stop; i+=step, k++)
        vec[k] = toCopyfrom[i];
     }
   else
     {
      for(class="type">long i = class="type">long(start), k = class="num">0; i>-class="num">1 && k<class="type">long(vec.Size()) && i>=class="type">long(stop); i-=class="type">long(step), k++)
        vec[k] = toCopyfrom[i];
     }
  }

class="kw">template<class="kw">typename T>
class="type">void rangetrend(matrix<T> &mat,T value=class="num">0.0,T step=class="num">1.0)
  {
   class="type">ulong r = mat.Rows();
   vector col1(r,arange,value,step);
   vector col2 = vector::Ones(r);
   if(!mat.Resize(r,class="num">2) || !mat.Col(col1,class="num">0) || !mat.Col(col2,class="num">1))
     {
      Print(__FUNCTION__ " matrix initialization error: ", GetLastError());
      class="kw">return;
     }
  }

class="kw">template<class="kw">typename T>
class="type">void olsdmatrix(matrix<T> &mat,class="kw">const T &toCopyfrom[],class="type">ulong start=class="num">0,class="type">ulong stop=ULONG_MAX, class="type">ulong step=class="num">1)
  {
   vector col0(class="num">1,assign,toCopyfrom,start,stop,step);
   class="type">ulong r = col0.Size();

把向量喂给 Hurst 计算前的搬运与差分

向量到数组的搬运看似琐碎,却是后面所有统计函数的必经路。vecToArray 先卡住空向量(Size<1 直接报错返回 false),再比对目标数组长度,不一致就 ArrayResize 到输入长度;任一环节失败都打印 GetLastError() 方便在 MT5 Experts 日志里定位。 difference 函数做的是一阶差分:用 slice 从原向量分别取 [1, Size-1] 和 [0, Size-2] 两段等长子向量,相减得到长度为 Size-1 的新序列。这一步对应价格行为里把收盘价序列转成收益率扰动,Hurst 估计对原始级数敏感,差分后能压住趋势项干扰。 general_hurst 的重载版接收 vector 引用,内部调 vecToArray 把数据倒进 double 数组,失败则返回 EMPTY_VALUE(宏值通常是 -1.7976931348623158e+308)。外汇与贵金属杠杆高、跳空频繁,差分后样本若含极端跳点,Hurst 指数可能偏向 0.5 伪随机,建议先用小周期 XAUUSD 的 M5 收盘序列手跑一遍验证。

MQL5 / C++
  if(!r)
    {
      Print(__FUNCTION__," failed to initialize first column ");
      class="kw">return;
    }
  vector col1 = vector::Ones(r);
  if(!mat.Resize(r,class="num">2) || !mat.Col(col0,class="num">0) || !mat.Col(col1,class="num">1))
    {
      Print(__FUNCTION__ " matrix initialization error: ", GetLastError());
      class="kw">return;
    }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|vector to array                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool vecToArray(class="kw">const vector &in, class="type">class="kw">double &out[])
  {
class=class="str">"cmt">//---
   if(in.Size()<class="num">1)
     {
       Print(__FUNCTION__," Empty vector");
       class="kw">return false;
     }
class=class="str">"cmt">//---
   if(class="type">ulong(out.Size())!=in.Size() && ArrayResize(out,class="type">int(in.Size()))!=class="type">int(in.Size()))
     {
       Print(__FUNCTION__," resize error ", GetLastError());
       class="kw">return false;
     }
class=class="str">"cmt">//---
   for(class="type">uint i = class="num">0; i<out.Size(); i++)
      out[i]=in[i];
class=class="str">"cmt">//---
   class="kw">return true;
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| difference a vector                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector difference(class="kw">const vector &in)
  {
class=class="str">"cmt">//---
   if(in.Size()<class="num">1)
     {
       Print(__FUNCTION__," Empty vector");
       class="kw">return vector::Zeros(class="num">1);
     }
class=class="str">"cmt">//---
   vector yy,zz;
class=class="str">"cmt">//---
   yy.Init(in.Size()-class="num">1,slice,in,class="num">1,in.Size()-class="num">1,class="num">1);
class=class="str">"cmt">//---
   zz.Init(in.Size()-class="num">1,slice,in,class="num">0,in.Size()-class="num">2,class="num">1);
class=class="str">"cmt">//---
   class="kw">return yy-zz;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+--------------------------------------------------------------------------+
class=class="str">"cmt">//|overloaded gen_hurst() function that works with series contained in vector|
class=class="str">"cmt">//+--------------------------------------------------------------------------+
class="type">class="kw">double general_hurst(vector &data, class="type">int q, class="type">int lower=class="num">0,class="type">int upper=class="num">0)
  {
   class="type">class="kw">double series[];
   if(!vecToArray(data,series))
      class="kw">return EMPTY_VALUE;
   else
让小布替你跑这套分形扫描
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 GHE 与方差比的双栏读数,你只需判断 q=2 附近的偏离方向。

常见问题

原版固定二阶矩,GHE 引入 q 参数遍历各阶矩,能分别刻画绝对偏差与长程依赖等不同统计特征。
GHE 给出分形分类倾向,VRT 提供统计显著性确认,避免把噪声误判为均值回复。
向量与矩阵初始化在多重滞后方差计算中反复出现,独立工具文件可减少重复代码与越界风险。
可以,品种页的 AIGC 分形模块同时绘制两阶矩曲线,并标注当前品种偏趋势或偏回复的概率。
K 应小于序列长度且避开周期边界,经验上取长度 1/4 到 1/2 区间做滚动验证更稳。