在 MQL5 中实现广义赫斯特指数和方差比检验·综合运用
📘

在 MQL5 中实现广义赫斯特指数和方差比检验·综合运用

第 3/3 篇

◍ 用 Z 分数把价格压回零轴附近

Z 分数(z-score)算的是价格偏离其均值多少个标准差,本质是把行情做归一化,让指标在 0 上下波动而不是跟着绝对价位跑。把这段逻辑写成指标后,曲线形态会比裸价更贴近正态分布,方便用统计阈值而不是肉眼判断极值。 当 Z 值大幅脱离 0、超过按历史波动定的阈值,就有可能出现均值回复机会:极负倾向做多,极正倾向做空,所以需要一负一正两个阈值分别管入场。出场可以用对称思路——空头等 Z 回到 0 附近平掉,多头从极端负值弹回 0 时也退场,不另设止损止盈。 下面这段 MQL5 是 Zscore 指标核心:默认周期 10,独立子窗口画一条蓝线。注意 OnCalculate 里用 vector 承接区间价格再做标准化,改 z_period 会直接挪动 PLOT_DRAW_BEGIN,新手可先调这个参数看曲线灵敏度。外汇与贵金属杠杆高,这类统计信号失效时回撤可能很快,实盘前务必在 MT5 用历史数据验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Zscore.mq5 |
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="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include<VectorMatrixTools.mqh>
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">1
class="macro">#class="kw">property indicator_plots   class="num">1
class=class="str">"cmt">//--- plot Zscore
class="macro">#class="kw">property indicator_label1  "Zscore"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrBlue
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input class="type">int      z_period = class="num">10;
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double      ZscoreBuffer[];
vector vct;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- indicator buffers mapping
   SetIndexBuffer(class="num">0,ZscoreBuffer,INDICATOR_DATA);
class=class="str">"cmt">//----
   PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0);
class=class="str">"cmt">//---
   PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,z_period-class="num">1);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],

Z分数计算与均值回归EA的参数骨架

这段自定义指标接口把 OHLC、成交量、点差数组以引用方式传入,对应 MT5 的 OnCalculate 标准签名。若可用 K 线数 rates_total 小于回看窗口 z_period,直接打印 Insufficient history 并返回 -1,避免后续除零或空向量运算。 循环里用 vct.Init 按 z_period 长度截取 close 子区间,当向量尺寸等于窗口才计算 ZscoreBuffer[i] = (close[i] - vct.Mean()) / vct.Std(),否则写 0.0。注意 limit 在首次计算时设为 z_period-1,后续用 prev_calculated-1 增量刷新,减少重复遍历。 EA 主体通过 #resource 引入编译好的 Zscore.ex5,输入参数给出明确可改点:PeriodLength=10 为 Z 分数回看周期,LongOpenLevel=-2.0 表示价格偏离均值超 2 个标准差时倾向开多,ShortOpenLevel=2.0 对称;平仓阈值收窄到 ±0.5。LotsSize=0.01、SlippagePoints=10、MagicNumber=123456 均为可直抄的默认档。 外汇与贵金属杠杆高,均值回归在趋势市会连续触发反向单,实盘前务必在 MT5 策略测试器用对应品种历史数据验证阈值灵敏度。

MQL5 / C++
class="kw">const class="type">class="kw">double &open[],
class="kw">const class="type">class="kw">double &high[],
class="kw">const class="type">class="kw">double &low[],
class="kw">const class="type">class="kw">double &close[],
class="kw">const class="type">long &tick_volume[],
class="kw">const class="type">long &volume[],
class="kw">const class="type">int &spread[])
{
class=class="str">"cmt">//---
 if(rates_total<z_period)
  {
   Print("Insufficient history");
   class="kw">return -class="num">1;
  }
class=class="str">"cmt">//---
 class="type">int limit;
 if(prev_calculated<=class="num">0)
   limit = z_period - class="num">1;
 else
   limit = prev_calculated - class="num">1;
class=class="str">"cmt">//---
 for(class="type">int i = limit; i<rates_total; i++)
  {
   vct.Init(class="type">ulong(z_period),assign,close,class="type">ulong(i-(z_period-class="num">1)),i,class="num">1);
   if(vct.Size()==class="type">ulong(z_period))
     ZscoreBuffer[i] = (close[i] - vct.Mean())/vct.Std();
   else
     ZscoreBuffer[i]=class="num">0.0;
  }
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
 class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     MeanReversion.mq5 |
class=class="str">"cmt">//|       Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#resource "\Indicators\Zscore.ex5"
class="macro">#include<ExpertTools.mqh>
class=class="str">"cmt">//---Input parameters
class="kw">input class="type">int   PeriodLength   = class="num">10;
class="kw">input class="type">class="kw">double LotsSize = class="num">0.01;
class="kw">input class="type">class="kw">double  LongOpenLevel = -class="num">2.0;
class="kw">input class="type">class="kw">double  ShortOpenLevel = class="num">2.0;
class="kw">input class="type">class="kw">double  LongCloseLevel = -class="num">0.5;
class="kw">input class="type">class="kw">double  ShortCloseLevel = class="num">0.5;
class="kw">input class="type">ulong   SlippagePoints = class="num">10;
class="kw">input class="type">ulong   MagicNumber    = class="num">123456;
class=class="str">"cmt">//---

「EA 骨架里怎么挂 Z-score 指标」

这段 MT5 专家顾问的初始化与 tick 逻辑,核心是把自编的 Zscore.ex5 指标句柄在安全重试机制下挂上,再在每笔报价里取信号、管持仓。外汇与贵金属杠杆高,回测通过不代表实盘能跑赢,任何信号都只是概率倾向。 OnInit 先卡死 PeriodLength 小于 2 的非法参数,直接返回 INIT_FAILED;随后调 InitializeIndicator(),失败也同样中断。OnTick 里用 GetSignal() 拿信号,若同魔法码同品种已有持仓就只在信号归零时平仓,否则开新仓——这是典型的单品种单策略互斥结构。 InitializeIndicator 用 while 循环最多重试 10 次去 iCustom 加载指标,句柄无效且剩余次数大于 0 就继续。注意原文判断写的是 if(try<0) 才报失败,但循环条件是 try>0,所以 try 最小到 0 就停,实际走到 try<0 的分支在逻辑上几乎不会触发,复制代码时建议改成 try<=0 才更严谨。

MQL5 / C++
class="type">int indi_handle;
class=class="str">"cmt">//---
class="type">class="kw">double zscore[class="num">2];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   if(PeriodLength<class="num">2)
     {
      Print("Invalid parameter value for PeriodLength");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//---
   if(!InitializeIndicator())
     class="kw">return INIT_FAILED;
class=class="str">"cmt">//---        
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---    
       class="type">int signal = GetSignal();
class=class="str">"cmt">//---    
       if(SumMarketOrders(MagicNumber,_Symbol,-class="num">1))
        {
         if(signal==class="num">0)
          CloseAll(MagicNumber,_Symbol,-class="num">1);
         class="kw">return;
        }
       else
         OpenPosition(signal);
class=class="str">"cmt">//---  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize indicator                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool InitializeIndicator(class="type">void)
{
indi_handle = INVALID_HANDLE;
class=class="str">"cmt">//---
class="type">int try = class="num">10;
class=class="str">"cmt">//---
while(indi_handle == INVALID_HANDLE && try>class="num">0)
  {
   indi_handle = (indi_handle==INVALID_HANDLE)?iCustom(NULL,PERIOD_CURRENT,"::Indicators\\Zscore.ex5",PeriodLength):indi_handle;
   try--;
  }
class=class="str">"cmt">//---
if(try<class="num">0)
  {
   Print("Failed to initialize Zscore indicator ");
   class="kw">return false;
  }
class=class="str">"cmt">//---
class="kw">return true;
}

◍ 信号函数与下单闸门的实现细节

把 Z-Score 指标缓冲读进数组后,真正的交易逻辑其实就藏在两段函数里。GetSignal 负责判定穿越,OpenPosition 负责拦掉重复开仓并发送订单,两者解耦后回测和实盘都能直接复用。 GetSignal 默认取 sig_shift=1,也就是用倒数第二根和倒数第一根做比较。CopyBuffer 只拉 2 根(sig_shift 起、长度 2),若返回值小于 2 说明拷贝失败,直接 Print 错误并返回 INT_MIN,避免脏信号进场。 进场判定很直白:上一根 zscore 低于 LongOpenLevel 且当前根上穿,返回 1(多);上一根高于 ShortOpenLevel 且当前根下穿,返回 -1(空)。平仓判定则看 LongCloseLevel / ShortCloseLevel 的反向穿越,返回 0。其余情况一律 INT_MIN,表示「这根不动」。 OpenPosition 先调 LastOrderOpenTime 比对本周期 K 线开盘时间 iTime(NULL,0,0),若同根已有同魔术码订单就 false 退出——这根闸门能挡掉一根 K 线内反复追单。确认无单后,sig=1 发 ORDER_TYPE_BUY,sig=-1 发 ORDER_TYPE_SELL,手数和滑点由 LotsSize、SlippagePoints 控制。 外汇与贵金属杠杆高、滑点跳空频繁,这套闸门只是过滤重复信号,不保证胜率;上 MT5 把 LongOpenLevel 等阈值打印出来核对,再跑一轮历史回放更稳妥。

MQL5 / C++
class="type">int GetSignal(class="kw">const class="type">int sig_shift=class="num">1)
{
class=class="str">"cmt">//---
if( CopyBuffer(indi_handle,class="type">int(class="num">0),sig_shift,class="type">int(class="num">2),zscore)<class="num">2)
  {
    Print(__FUNCTION__," Error copying from indicator buffers: ", GetLastError());
    class="kw">return INT_MIN;
  }
class=class="str">"cmt">//---   
if(zscore[class="num">1]<LongOpenLevel && zscore[class="num">0]>LongOpenLevel)
    class="kw">return (class="num">1);
class=class="str">"cmt">//---   
if(zscore[class="num">1]>ShortOpenLevel && zscore[class="num">0]<ShortOpenLevel)
    class="kw">return (-class="num">1);            
class=class="str">"cmt">//---   
if((zscore[class="num">1]>LongCloseLevel && zscore[class="num">0]<LongCloseLevel) ||
   (zscore[class="num">1]<ShortCloseLevel && zscore[class="num">0]>ShortCloseLevel))
    class="kw">return (class="num">0);
class=class="str">"cmt">//---
class="kw">return INT_MIN;
class=class="str">"cmt">//--- 
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Go class="type">long or class="type">short                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool OpenPosition(class="kw">const class="type">int sig)
{
class="type">long pid;
class=class="str">"cmt">//--- 
if(LastOrderOpenTime(pid,NULL,MagicNumber)>=iTime(NULL,class="num">0,class="num">0))
  class="kw">return false;
class=class="str">"cmt">//---   
if(sig==class="num">1)
  class="kw">return SendOrder(_Symbol,class="num">0,ORDER_TYPE_BUY,LotsSize,SlippagePoints,class="num">0,class="num">0,NULL,MagicNumber);
else
 if(sig==-class="num">1)
   class="kw">return SendOrder(_Symbol,class="num">0,ORDER_TYPE_SELL,LotsSize,SlippagePoints,class="num">0,class="num">0,NULL,MagicNumber);
class=class="str">"cmt">//--- 
  class="kw">return false;             
}

用半衰期判断该不该做均值回归

半衰期衡量价格偏离历史均值后,回到均值一半位置所需的时间。公式上 HL = -log(2) / lambda,lambda 是回归速度:lambda 越负、绝对值越大,半衰期越短,回归越快;lambda 为正就别碰均值回归,连接近零的负 lambda 也不建议上,因为那代表半衰期长到策略基本无效。 实盘筛选时,把 GHE、VRT 和半衰期三件套跑在 EURUSD、GBPUSD、USDCHF、USDJPY 的日线样本上,USDCHF 在所选日期内的测试结果最理想,于是用它优化 EA。有趣的是,优化出来的最佳 Zscore 周期和计算出的半衰期非常接近,这至少说明历史样本里均值回归特征自洽。 但样本外测试表现并不好,外汇和贵金属本身高风险,市场状态漂移会让优化期学到的特征失效。需要更动态的进场退出阈值来跟住基础波动变化,或者把工具搬去价差配对交易,用两个协整品种的价差算 Zscore 而非单序列。 下面这段 MQL5 是半衰期的核心实现,思路是对价格序列做差分后回归,lambda 取回归 beta,再用 -log(2)/lambda 得出结果: //+------------------------------------------------------------------+

//Calculate Half life of Mean reversion

//+------------------------------------------------------------------+ double mean_reversion_half_life(vector &data, double &lambda) { //--- vector yy,zz; matrix xx; //--- OLS ols_reg; //--- yy.Init(data.Size()-1,slice,data,1,data.Size()-1,1); //--- zz.Init(data.Size()-1,slice,data,0,data.Size()-2,1); //---

if(!xx.Init(zz.Size(),2)!xx.Col(zz,0)!xx.Col(vector::Ones(zz.Size()),1)!ols_reg.Fit(yy-zz,xx))

{ Print(__FUNCTION__," Error in calculating half life of mean reversion ", GetLastError()); return 0; } //--- vector params = ols_reg.ModelParameters(); lambda = params[0]; //--- return (-log(2)/lambda); //--- } 逐行拆解:yy 取原序列从第 1 位到末尾的子向量,zz 取第 0 位到倒数第 2 位的滞后一期子向量;xx 是两列矩阵,第一列放 zz,第二列放全 1 做截距;ols_reg.Fit(yy-zz, xx) 实际是对「当期减前期」的差分序列做带截距回归;params[0] 即 beta 也就是 lambda;最后 -log(2)/lambda 直接出半衰期。 配套测试脚本头部长这样,改 Symbols 和起止日期就能换品种扫一遍: //+------------------------------------------------------------------+

//SymbolTester.mq5
//Copyright 2024, MetaQuotes Ltd.
//[MQL5官方文档]

//+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "[MQL5官方文档] #property version "1.00" #property script_show_inputs #include<MeanReversionUtilities.mqh> #include<GHE.mqh> #include<VRT.mqh> //--- input parameters input string Symbols = "EURUSD,GBPUSD,USDCHF,USDJPY";//Comma separated list of symbols to test input ENUM_TIMEFRAMES TimeFrame = PERIOD_D1; input datetime StartDate=D'2020.01.02 00:00:01'; input datetime StopDate=D'2015.01.18 00:00:01'; 注意 StopDate 写在了 StartDate 之前,实跑前先把这两个日期改对,否则序列取出来是空的。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Calculate Half life of Mean reversion                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double mean_reversion_half_life(vector &data, class="type">class="kw">double &lambda)
  {
class=class="str">"cmt">//---
   vector yy,zz;
   matrix xx;
class=class="str">"cmt">//---
   OLS ols_reg;
class=class="str">"cmt">//---
   yy.Init(data.Size()-class="num">1,slice,data,class="num">1,data.Size()-class="num">1,class="num">1);
class=class="str">"cmt">//---
   zz.Init(data.Size()-class="num">1,slice,data,class="num">0,data.Size()-class="num">2,class="num">1);
class=class="str">"cmt">//---
   if(!xx.Init(zz.Size(),class="num">2) || !xx.Col(zz,class="num">0) || !xx.Col(vector::Ones(zz.Size()),class="num">1) || !ols_reg.Fit(yy-zz,xx))
     {
       Print(__FUNCTION__," Error in calculating half life of mean reversion ", GetLastError());
       class="kw">return class="num">0;
     }
class=class="str">"cmt">//---
   vector params = ols_reg.ModelParameters();
   lambda = params[class="num">0];
class=class="str">"cmt">//---
   class="kw">return (-log(class="num">2)/lambda);
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                        SymbolTester.mq5 |
class=class="str">"cmt">//|           Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|             [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property script_show_inputs
class="macro">#include<MeanReversionUtilities.mqh>
class="macro">#include<GHE.mqh>
class="macro">#include<VRT.mqh>
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input class="type">class="kw">string   Symbols = "EURUSD,GBPUSD,USDCHF,USDJPY";class=class="str">"cmt">//Comma separated list of symbols to test
class="kw">input ENUM_TIMEFRAMES TimeFrame = PERIOD_D1;
class="kw">input class="type">class="kw">datetime StartDate=D&class="macro">#x27;class="num">2020.01.class="num">02 class="num">00:class="num">00:class="num">01&class="macro">#x27;;
class="kw">input class="type">class="kw">datetime StopDate=D&class="macro">#x27;class="num">2015.01.class="num">18 class="num">00:class="num">00:class="num">01&class="macro">#x27;;

「把方差比和赫斯特塞进同一段脚本」

这段脚本把单品种的方差比检验(Variance Ratio)与广义赫斯特指数(GHE)跑在同一个 OnStart 里,省去来回切图表。输入参数里 Q_parameter=2 控制 GHE 的 q 阶矩,MinimumLag=2 与 MaximumLag=100 分别作为方差比检验的短、长滞后窗口,ApplyLogTransformation=true 会对收盘价先做对数变换再检验。 外层循环用 StringSplit 按逗号拆解 Symbols 列表,逐个品种取 Close 序列。若列表末尾带逗号,代码会 num_symbols-- 丢掉空串;若拆分后不足 1 个品种则直接 Print 报错退出,避免空跑。 每个品种先 CopyClose 拉数据,可选走 MathLog;随后两次调用 vrt.Vrt(prices, lag) —— 分别传 MinimumLag 与 MaximumLag,取出 vlower、vupper 两个 VRatio 值。接着 general_hurst 算 GHE,mean_reversion_half_life 算均值回复半衰期 hl 与衰减系数 lb,最后一行 Print 把 GHE、双边界方差比、HalfLife、Lambda 全部打印到日志。 复制时留意:StartDate/StopDate 与 TimeFrame 应在别处声明,原文未贴;若 StartDate<=StopDate 会触发「Invalid input」并 return,实际应保证起始索引大于终止索引(MT5 历史读取惯例)。外汇与贵金属属高杠杆品种,半衰期与方差比仅描述历史统计特征,样本外可能失效,勿直接当作方向依据。

MQL5 / C++
class="kw">input class="type">int Q_parameter = class="num">2;
class="kw">input class="type">int MinimumLag = class="num">2;
class="kw">input class="type">int MaximumLag = class="num">100;
class="kw">input class="type">bool ApplyLogTransformation = true;
class=class="str">"cmt">//---
CVarianceRatio vrt;
class="type">class="kw">double ghe,hl,lb,vlower,vupper;
class="type">class="kw">double prices[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---Check Size class="kw">input value
   if(StartDate<=StopDate)
     {
      Print("Invalid class="kw">input for StartDater or StopDate");
      class="kw">return;
     }
class=class="str">"cmt">//---array for symbols
   class="type">class="kw">string symbols[];
class=class="str">"cmt">//---process list of symbols from user class="kw">input
   class="type">int num_symbols = StringSplit(Symbols,StringGetCharacter(",",class="num">0),symbols);
class=class="str">"cmt">//---incase list contains ending comma
   if(symbols[num_symbols-class="num">1]=="")
      num_symbols--;
class=class="str">"cmt">//---in case there are less than two symbols specified
   if(num_symbols<class="num">1)
     {
      Print("Invalid class="kw">input. Please list at least one symbol");
      class="kw">return;
     }
class=class="str">"cmt">//---loop through all paired combinations from list
   for(class="type">uint i=class="num">0; i<symbols.Size(); i++)
     {
      class=class="str">"cmt">//--- get prices for the pair of symbols
      if(CopyClose(symbols[i],TimeFrame,StartDate,StopDate,prices)<class="num">1)
        {
         Print("Failed to copy close prices ", ::GetLastError());
         class="kw">return;
        }
      class=class="str">"cmt">//---
      if(ApplyLogTransformation && !MathLog(prices))
        {
         Print("Mathlog error ", GetLastError());
         class="kw">return;
        }
      class=class="str">"cmt">//---
      if(!vrt.Vrt(prices,MinimumLag))
         class="kw">return;
      class=class="str">"cmt">//---
      vlower = vrt.VRatio();
      class=class="str">"cmt">//---
      if(!vrt.Vrt(prices,MaximumLag))
         class="kw">return;
      class=class="str">"cmt">//---
      vupper = vrt.VRatio();
      class=class="str">"cmt">//---
      ghe = general_hurst(prices,Q_parameter,MinimumLag,MaximumLag);
      class=class="str">"cmt">//---
      hl = mean_reversion_half_life(prices,lb);
      class=class="str">"cmt">//--- output the results
      Print(symbols[i], " GHE:  ", DoubleToString(ghe)," | Vrt: ",DoubleToString(vlower)," ** ",DoubleToString(vupper)," | HalfLife ",DoubleToString(hl)," | Lambda: ",DoubleToString(lb));
     }
   }

◍ USDCHF日线下的跨品种半衰期扫描

在 MT5 策略测试器里以 USDCHF 的 D1 周期跑 SymbolTester,能直接拿到一组跨品种协整特征数。下面四条日志是同一批次的输出,时间戳集中在 19:31:03 秒内,说明计算开销极低。 EURUSD 的 GHE 读数为 0.44755644,HalfLife 85.6 根 K 线,Lambda -0.008097;GBPUSD 的 HalfLife 拉长到 201.38,Lambda 仅 -0.00344199,均值回复更慢。USDCHF 自身 HalfLife 28.9 最短,Lambda -0.02397976 绝对值最大,日线级别回归倾向最强。USDJPY 的 Vrt 与另一乘积项分别为 0.99875744 与 1.06103587,HalfLife 132.66。 这些数字只描述历史样本的统计属性,不预示未来。外汇与贵金属杠杆高,实盘前务必用 MT5 自测一遍参数,别直接信别人贴的日志。

MQL5 / C++
class="num">19:class="num">31:class="num">03.143     SymbolTester(USDCHF,D1)        EURUSD GHE:  class="num">0.44755644 | Vrt: class="num">0.97454284 ** class="num">0.61945905 | HalfLife class="num">85.60548208 | Lambda: -class="num">0.00809700
class="num">19:class="num">31:class="num">03.326     SymbolTester(USDCHF,D1)        GBPUSD GHE:  class="num">0.46304381 | Vrt: class="num">1.01218672 ** class="num">0.82086185 | HalfLife class="num">201.38001205 | Lambda: -class="num">0.00344199
class="num">19:class="num">31:class="num">03.509     SymbolTester(USDCHF,D1)        USDCHF GHE:  class="num">0.42689382 | Vrt: class="num">1.02233286 ** class="num">0.47888803 | HalfLife class="num">28.90550869 | Lambda: -class="num">0.02397976
class="num">19:class="num">31:class="num">03.694     SymbolTester(USDCHF,D1)        USDJPY GHE:  class="num">0.49198795 | Vrt: class="num">0.99875744 ** class="num">1.06103587 | HalfLife class="num">132.66433924 | Lambda: -class="num">0.00522482

画得少,看得清

把广义赫斯特指数、方差比检验和半衰期三件工具塞进 MT5,核心文件其实就那几个:GHE.mqh 装指数计算,VRT.mqh 封装方差比类,MeanReversionUtilities.mqh 里藏着半衰期函数,外加 SymbolTester.mq5 脚本可直接对交易品种跑均值回归探测。 真要验证,下 GHE.ex5(约 342 KB)挂到图表,切几个货币对和 XAUUSD 看 H 值落点;H 明显小于 0.5 的品种才倾向有均值回复特征,外汇与贵金属杠杆高、回撤可能超预期,别凭单一指标下重仓。 文件清单里 Zscore.mq5 才 2.68 KB,却撑起了 EA 的标准分监控——工具轻量,逻辑透明,剩下的只是你愿不愿意亲手跑一遍。

常见问题

看 Z 分数是否突破你设定的阈值(如 ±2),同时结合半衰期:半衰期短、Z 分数极端,才倾向有回归机会,外汇贵金属高风险勿重仓。
至少留 Z 窗口长度、阈值倍数、半衰期上限三个输入项,方便后续不改结构只调参验证。
小布可自动对品种跑 Z 分数与半衰期诊断,把极端偏离和回归概率直接标在盘面上,你只需看信号决定是否下手。
重点看方差比是否接近 1、赫斯特是否低于 0.5,两者同显均值回归倾向时,信号可信度更高。
信号只算该不该做,闸门控手数和禁开条件,回测时改风控不用动逻辑,调参更干净。