您应该知道的 MQL5 向导技术(第 01 部分):回归分析·进阶篇
📈

您应该知道的 MQL5 向导技术(第 01 部分):回归分析·进阶篇

(2/3)· 多数 EA 困在滞后指标里,本篇拆解如何用回归分析自建信号类突破瓶颈

案例拆解新手友好 第 2/3 篇
向导自带的信号类大多基于滞后指标,直接套用很难跳出同质化策略。把回归分析接进自定义信号,是用历史价格关系推算后续变动的一条可行路径。先理解联立方程怎么解,再谈写类。

◍ 双信号类的多空判定与回归求解

CSignalDUAL_RA 这个类把多空触发收敛到同一个 CheckDetermination 调用上。LongCondition 里若返回值大于 0 就直接返回该值,否则给 0;ShortCondition 则只对小于 0 的返回值取绝对值后返回,等于把同一套判定分别映射成多、空两个出口。 看 GetY 能发现它在做线性回归式的矩阵求解。先用 Data(r,close) 填列向量 _b,再用 Data(r+c+1,close) 填 m_size×m_size 的矩阵 _a,最后走 RMatrixSolve 拿系数数组 _x,再乘回 Data(r,close) 累加出 _y。 [CODE]double Data(int ind,bool close); // }; //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+ int CSignalDUAL_RA::LongCondition(void) { int _check=CheckDetermination(0,PositionSelect(m_symbol.Name())); if(_check>0){ return(_check); } return(0); } //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+ int CSignalDUAL_RA::ShortCondition(void) { int _check=CheckDetermination(0,PositionSelect(m_symbol.Name())); if(_check<0){ return((int)fabs(_check)); } return(0); } //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+ double CSignalDUAL_RA::GetY(int ind,bool close) { double _y=0.0; CMatrixDouble _a;_a.Resize(m_size,m_size); double _b[];ArrayResize(_b,m_size);ArrayInitialize(_b,0.0); for(int r=0;r<m_size;r++) { _b[r]=Data(r,close); for(int c=0;c<m_size;c++) { _a[r].Set(c,Data(r+c+1, close)); } } int _info=0; CDenseSolver _S; CDenseSolverReport _r; double _x[];ArrayResize(_x,m_size);ArrayInitialize(_x,0.0); _S.RMatrixSolve(_a,m_size,_b,_info,_r,_x); for(int r=0;r<m_size;r++) { _y+=(Data(r,close)*_x[r]); } //--- return(_y); } enum Edata {[/CODE] 逐行拆解:第 1 行声明 Data 方法取第 ind 根 K 线、close 控制取收盘价还是其他价;LongCondition 中 PositionSelect 先确认品种有无持仓再喂给判定函数;ShortCondition 用 fabs 把负信号转正整数避免越界;GetY 里 _a.Resize(m_size,m_size) 决定回归窗口,_S.RMatrixSolve 是稠密矩阵求解器,最后 _y 是拟合值。 开 MT5 把 m_size 从默认改到 8 或 12,看 GetY 返回的拟合曲线对 XAUUSD 的 M15 是否更跟手;外汇与贵金属杠杆高,信号仅作概率参考,实盘前务必用策略测试器跑历史数据。

MQL5 / C++
class="type">class="kw">double Data(class="type">int ind,class="type">bool close);
class=class="str">"cmt">//
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalDUAL_RA::LongCondition(class="type">void)
  {
    class="type">int _check=CheckDetermination(class="num">0,PositionSelect(m_symbol.Name()));
    if(_check>class="num">0){ class="kw">return(_check); }
    
    class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalDUAL_RA::ShortCondition(class="type">void)
  {
    class="type">int _check=CheckDetermination(class="num">0,PositionSelect(m_symbol.Name()));
    if(_check<class="num">0){ class="kw">return((class="type">int)fabs(_check)); }
    
    class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalDUAL_RA::GetY(class="type">int ind,class="type">bool close)
 {
    class="type">class="kw">double _y=class="num">0.0;
    
    CMatrixDouble _a;_a.Resize(m_size,m_size);
    class="type">class="kw">double _b[];ArrayResize(_b,m_size);ArrayInitialize(_b,class="num">0.0);
    
    for(class="type">int r=class="num">0;r<m_size;r++)
    {
     _b[r]=Data(r,close);
     
     for(class="type">int c=class="num">0;c<m_size;c++)
     {
      _a[r].Set(c,Data(r+c+class="num">1, close));
     }
    }
    
    class="type">int _info=class="num">0;
    CDenseSolver _S;
    CDenseSolverReport _r;
    class="type">class="kw">double _x[];ArrayResize(_x,m_size);ArrayInitialize(_x,class="num">0.0);
    
    _S.RMatrixSolve(_a,m_size,_b,_info,_r,_x);
    
    for(class="type">int r=class="num">0;r<m_size;r++)
    {
     _y+=(Data(r,close)*_x[r]);
    }
    class=class="str">"cmt">//---
    class="kw">return(_y);
  }
enum Edata
  {

「双均线差与误差项的代码拆解」

这个类把信号数据分成两类:DATA_TREND=0 表示取两条移动平均线的差值变化,DATA_RANGE=1 表示取每根 K 线自身高低区间的变化。开仓前与收盘后的计算路径完全一致,只是分别受 m_open_data / m_close_data 控制。 Data() 函数里,若选 TREND 模式,会先 m_h_ma.Refresh(-1) 强制刷新高位均线,再返回(低轨均线差 − 高轨均线差);选 RANGE 模式则直接用 Low 与 High 的差值相减,不涉及均线对象。两种模式若条件都不命中,兜底返回 0.0。 误差计算 GetE() 提供两种口径:ERROR_STANDARD=1 走样本标准误,循环 m_size 次累加 (Data−GetY)^2,再开平方除以 (m_size−1) 后除以 sqrt(m_size);ERROR_LAST=0 则只取当前柱的 Data(ind)−GetY(ind+1) 单点偏差。外汇与贵金属波动跳变频繁,标准误口径在 m_size 较小时可能放大噪声,建议先在 MT5 用 EURUSD 的 M15 把 m_size 调到 30 以上观察曲线。

MQL5 / C++
   DATA_TREND=class="num">0,         class=class="str">"cmt">// changes in moving average close
   DATA_RANGE=class="num">1          class=class="str">"cmt">// changes in close
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalDUAL_RA::Data(class="type">int ind,class="type">bool close)
  {
if(!close)
{
if(Edata(m_open_data)==DATA_TREND)
{
m_h_ma.Refresh(-class="num">1);
class="kw">return((m_l_ma.Main(StartIndex()+ind)-m_l_ma.Main(StartIndex()+ind+class="num">1))-(m_h_ma.Main(StartIndex()+ind)-m_h_ma.Main(StartIndex()+ind+class="num">1)));
}
else if(Edata(m_open_data)==DATA_RANGE)
{
class="kw">return((Low(StartIndex()+ind)-Low(StartIndex()+ind+class="num">1))-(High(StartIndex()+ind)-High(StartIndex()+ind+class="num">1)));
}
}
else if(close)
{
if(Edata(m_close_data)==DATA_TREND)
{
m_h_ma.Refresh(-class="num">1);
class="kw">return((m_l_ma.Main(StartIndex()+ind)-m_l_ma.Main(StartIndex()+ind+class="num">1))-(m_h_ma.Main(StartIndex()+ind)-m_h_ma.Main(StartIndex()+ind+class="num">1)));
}
else if(Edata(m_close_data)==DATA_RANGE)
{
class="kw">return((Low(StartIndex()+ind)-Low(StartIndex()+ind+class="num">1))-(High(StartIndex()+ind)-High(StartIndex()+ind+class="num">1)));
}
}

   class="kw">return(class="num">0.0);
  }
enum Eerror
  {
   ERROR_LAST=class="num">0,         class=class="str">"cmt">// use the last error
   ERROR_STANDARD=class="num">1      class=class="str">"cmt">// use standard error
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalDUAL_RA::GetE(class="type">int ind,class="type">bool close)
  {
   if(!close)
   {
     if(Eerror(m_open_error)==ERROR_STANDARD)
      {
       class="type">class="kw">double _se=class="num">0.0;
       for(class="type">int r=class="num">0;r<m_size;r++) { _se+=pow(Data(r,close)-GetY(r+class="num">1,close),class="num">2.0); }
       _se=sqrt(_se/(m_size-class="num">1)); _se=_se/sqrt(m_size); class="kw">return(_se);
      }
     else if(Eerror(m_open_error)==ERROR_LAST){ class="kw">return(Data(ind,close)-GetY(ind+class="num">1,close)); }
   }
   else if(close)
   {
     if(Eerror(m_close_error)==ERROR_STANDARD)
      {
       class="type">class="kw">double _se=class="num">0.0;
       for(class="type">int r=class="num">0;r<m_size;r++){  _se+=pow(Data(r,close)-GetY(r+class="num">1,close),class="num">2.0); }
       _se=sqrt(_se/(m_size-class="num">1)); _se=_se/sqrt(m_size); class="kw">return(_se);
      }
     else if(Eerror(m_close_error)==ERROR_LAST){ class="kw">return(Data(ind,close)-GetY(ind+class="num">1,close)); }
   }
class=class="str">"cmt">//---

共线修正怎么塞进信号求值

DUAL_RA 信号类用 CheckCollinearity 在开盘与收盘两套逻辑里做共线性折扣。核心不是简单相加,而是先拿斯皮尔曼相关系数算出一个惩罚系数 _c,再按枚举分支决定 y 与误差各自被乘多少权重。 _c 的算式是 1.0/(2.0+fmin(-1.0, MathCorrelationSpearman(_array_1,_array_2,_r)))。当两段序列完全秩相关时 Spearman 接近 1,_c 被压到 1/3 附近;若相关极弱,_c 会贴近 1,相当于不打折。 枚举 Echeck 给了四种模式:CHECK_Y 只折扣 y,CHECK_E 只折扣误差,CHECK_ALL 对两者之和折扣,CHECK_NONE 完全不处理共线。实盘里黄金 1H 在趋势中段常出现 y 与误差同向放大,用 CHECK_ALL 可能把信号削掉 40% 以上,建议开 MT5 把 m_open_collinearity 切到 CHECK_NONE 对比裸信号曲线。 下面这段是原文里 CheckCollinearity 的主体,逐行看逻辑落点: enum Echeck { CHECK_Y=0, // 只检查 y CHECK_E=1, // 只检查误差 CHECK_ALL=2, // y 和误差都检查 CHECK_NONE=-1 // 不做共线检查 }; double CSignalDUAL_RA::CheckCollinearity(int ind,bool close) { double _check=0.0; // 最终返回值初始化 double _c=0.0,_array_1[],_array_2[],_r=0.0; // _c折扣系数,_r相关系数容器 ArrayResize(_array_1,m_size);ArrayResize(_array_2,m_size); // 两段序列定长 ArrayInitialize(_array_1,0.0);ArrayInitialize(_array_2,0.0); for(int s=0; s<m_size; s++) // 填前段与后段数据 { _array_1[s]=Data(ind+s,close); _array_2[s]=Data(m_size+ind+s,close); } _c=1.0/(2.0+fmin(-1.0,MathCorrelationSpearman(_array_1,_array_2,_r))); // 算折扣 double _i=Data(m_size+ind,close), _y=GetY(ind,close), _e=GetE(ind,close); if(!close) // 开盘分支 { if(Echeck(m_open_collinearity)==CHECK_Y){ _check=_i+(_c*_y)+_e; } else if(Echeck(m_open_collinearity)==CHECK_E){ _check=_i+_y+(_c*_e); } else if(Echeck(m_open_collinearity)==CHECK_ALL){ _check=_i+(_c*(_y+_e)); } else if(Echeck(m_open_collinearity)==CHECK_NONE){ _check=_i+(_y+_e); } } else if(close) // 收盘分支 { if(Echeck(m_close_collinearity)==CHECK_Y){ _check=_i+(_c*_y)+_e; } else if(Echeck(m_close_collinearity)==CHECK_E){ _check=_i+_y+(_c*_e); } else if(Echeck(m_close_collinearity)==CHECK_ALL){ _check=_i+(_c*(_y+_e)); } else if(Echeck(m_close_collinearity)==CHECK_NONE){ _check=_i+(_y+_e); } } return(_check); } 外汇与贵金属杠杆高,共线折扣只改信号幅度不改方向概率,回测通过不代表 live 能复现,参数切换前先跑样本外。

MQL5 / C++
enum Echeck
{
   CHECK_Y=class="num">0,      class=class="str">"cmt">// check for y only
   CHECK_E=class="num">1,      class=class="str">"cmt">// check for the error only
   CHECK_ALL=class="num">2,    class=class="str">"cmt">// check for both the y and the error
   CHECK_NONE=-class="num">1   class=class="str">"cmt">// do not use collinearity checks
};
class="type">class="kw">double CSignalDUAL_RA::CheckCollinearity(class="type">int ind,class="type">bool close)
{
   class="type">class="kw">double _check=class="num">0.0;
   class="type">class="kw">double _c=class="num">0.0,_array_1[],_array_2[],_r=class="num">0.0;
   ArrayResize(_array_1,m_size);ArrayResize(_array_2,m_size);
   ArrayInitialize(_array_1,class="num">0.0);ArrayInitialize(_array_2,class="num">0.0);
   for(class="type">int s=class="num">0; s<m_size; s++)
   {
      _array_1[s]=Data(ind+s,close);
      _array_2[s]=Data(m_size+ind+s,close);
   }
   _c=class="num">1.0/(class="num">2.0+fmin(-class="num">1.0,MathCorrelationSpearman(_array_1,_array_2,_r)));
class="type">class="kw">double   _i=Data(m_size+ind,close),    class=class="str">"cmt">//y intercept
_y=GetY(ind,close),           class=class="str">"cmt">//product sum of x and its B coefficients
_e=GetE(ind,close);           class=class="str">"cmt">//error
if(!close)
{
if(Echeck(m_open_collinearity)==CHECK_Y){ _check=_i+(_c*_y)+_e;          }
else if(Echeck(m_open_collinearity)==CHECK_E){ _check=_i+_y+(_c*_e);     }
else if(Echeck(m_open_collinearity)==CHECK_ALL){ _check=_i+(_c*(_y+_e)); }
else if(Echeck(m_open_collinearity)==CHECK_NONE){ _check=_i+(_y+_e);     }
}
else if(close)
{
if(Echeck(m_close_collinearity)==CHECK_Y){ _check=_i+(_c*_y)+_e;          }
else if(Echeck(m_close_collinearity)==CHECK_E){ _check=_i+_y+(_c*_e);     }
else if(Echeck(m_close_collinearity)==CHECK_ALL){ _check=_i+(_c*(_y+_e)); }
else if(Echeck(m_close_collinearity)==CHECK_NONE){ _check=_i+(_y+_e);     }
}
   class="kw">return(_check);
}

◍ 用判定系数给信号加一道闸门

回归系统里那个常被称作 R 平方的系数,量的是自变量 x 对 y 的解释力度。它在实际交易里的价值很直接:当区间里回归衰减明显时,系数走低意味着价格脱离模型轨道,远离市场更稳妥;系数够高,才说明这套拟合还可靠,值得下注。 具体阈值由两个输入参数控着——m_open_determination 与 m_close_determination,按持仓方向分开设。经验上系数大于 0 才有意义,理想值是 1;低于设定参数,CheckDetermination 直接让多空条件返回 0,信号作废。 下面这段是判定系数的计算核心。先刷新两条均线句柄,再用残差平方和 _ss_res 与总平方和 _ss_tot 算出 _det = 1 - 残差/总计。只有 _det 不低于开仓阈值,才继续算阈值与共线强度,最后把信号压缩成 -100~100 的整数返回。 外汇与贵金属杠杆高、滑点跳空频繁,系数过滤只是降低衰败期误触概率,不消除风险,MT5 里把 m_size 和判定阈值联调一遍最能体会边界。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalDUAL_RA::CheckDetermination(class="type">int ind,class="type">bool close)
  {
      class="type">int _check=class="num">0;
      m_h_ma.Refresh(-class="num">1);m_l_ma.Refresh(-class="num">1);
      class="type">class="kw">double _det=class="num">0.0,_ss_res=class="num">0.0,_ss_tot=class="num">0.0;
      for(class="type">int r=class="num">0;r<m_size;r++)
      {
         _ss_res+=pow(Data(r,close)-GetY(r+class="num">1,close),class="num">2.0); 
         _ss_tot+=pow(Data(r,close)-((m_l_ma.Main(r)-m_l_ma.Main(r+class="num">1))-(m_h_ma.Main(r)-m_h_ma.Main(r+class="num">1))),class="num">2.0);
      }
      
      if(_ss_tot!=class="num">0.0)
      {
         _det=(class="num">1.0-(_ss_res/_ss_tot));
         if(_det>=m_open_determination)
         {
            class="type">class="kw">double _threshold=class="num">0.0;
            for(class="type">int r=class="num">0; r<m_size; r++){ _threshold=fmax(_threshold,fabs(Data(r,close))); }
         
            class="type">class="kw">double _y=CheckCollinearity(ind,close);
            
            _check=class="type">int(round(class="num">100.0*_y/fmax(fabs(_y),fabs(_threshold))));
         }
      }
class=class="str">"cmt">//---
      class="kw">return(_check);
  }
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到回归残差和β漂移,你专注判断信号类要不要重写。

常见问题

现成信号类多基于滞后指标,难以覆盖特殊入场逻辑;自建类才能把回归分析等定制方法接进 EA。
可以,小布盯盘对应品种页内置了残差与系数监控,能直观看到β漂移和拟合退化,减少手动核对。
它用 LU 分解求解联立方程里的β系数,比手算高效,来自函数库 solvers.mqh 的 CDenseSolver。
价格序列受突发事件扰动,回归关系可能断裂,信号失效概率偏高,仅作概率参考而非方向保证。