相关性在交易中的实际应用·进阶篇
📊

相关性在交易中的实际应用·进阶篇

(2/3)· 多数交易者只看价格方向,却漏掉了变量间依赖强度这一层概率信号

新手友好 第 2/3 篇

把两根均线和一张走势图当作全部依据,是日内交易者最常见的盲区。价格下跌途中偶尔冒出的反抽烛形,到底只是噪音还是趋势衰弱,肉眼很难给出概率意义上的判断。相关性这个统计量,恰好能补上「方向有了、强度未知」的那块拼图。

◍ Pearson 与 Spearman 在 OnCalculate 里的两套迭代逻辑

自定义指标里相关系数计算分两条路:第一版 OnCalculate 直接拿 price 原始序列喂给 PearsonCalc,循环从 i=0 到 limit 正向跑,每次取 rangeN 个价格点算线性相关系数,适合快速看价格自身的线性黏合度。 第二版换了 Spearman 思路,先把 price[i+k]*multiply 强转成 int 再 RankPrices 排名,最后 SpearmanCalc 用 sumd2+=MathPow(Ranks[i]-i-1,2) 累计秩差平方。它从 i=limit 倒序写到 0,避免重复覆盖缓冲区,外汇与贵金属价格序列常有跳空,秩相关比线性相关更抗异常值,但仍是概率性参考,实盘高风险。 两段都靠 prev_calculated 控制重算范围:首跑 limit=rates_total-2-rangeN-begin,后续只补算新柱。开 MT5 把 rangeN 设成 20、multiply 设 10000,能直接对比两种系数在 EURUSD 的 H1 上分歧。

MQL5 / C++
class="type">class="kw">double PearsonCalc(class="type">class="kw">double &Ranks[],class="type">int N)
  {
   class="type">class="kw">double ch,zn;
   ch=Numerator(Ranks,N);
   zn=Denominator(Ranks,N);
   class="kw">return (ch/zn);
  }

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">int begin,
                class="kw">const class="type">class="kw">double &price[]
                )
  {
   if(rates_total<rangeN+begin)
      class="kw">return(class="num">0);
   class="type">int limit;
   if(prev_calculated>rates_total || prev_calculated<=class="num">0)
     {
      limit=rates_total-class="num">2-rangeN-begin;
      if(begin>class="num">0)
         PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,rangeN+begin);
     }
   else
      limit=rates_total-prev_calculated;
   ArraySetAsSeries(price,true);
   for(class="type">int i=limit; i>=class="num">0; i--)
     {
      for(class="type">int k=class="num">0; k<rangeN; k++)
         PriceInt[k]=class="type">int(price[i+k]*multiply);
      RankPrices(TrueRanks,PriceInt);
      ExtLineBuffer[i]=SpearmanCalc(R2,rangeN);
     }
   class="kw">return(rates_total);
  }

class="type">class="kw">double SpearmanCalc(class="type">class="kw">double &Ranks[],class="type">int N)
  {
   class="type">class="kw">double sumd2=class="num">0.0;
   for(class="type">int i=class="num">0; i<N; i++)
      sumd2+=MathPow(Ranks[i]-i-class="num">1,class="num">2);

「Kendall tau 指标的 OnCalculate 与计算内核」

这段 MT5 自定义指标把 Kendall 秩相关系数塞进价格序列里,用来衡量一段窗口内走势的单调协同程度。外汇与贵金属波动高、跳空频繁,这类非参数指标比皮尔逊相关更抗异常值,但信号滞后倾向随 rangeN 增大而上升。 OnCalculate 先卡掉数据不足的情况:rates_total 小于 rangeN+begin 直接返 0。首次计算时 limit 取 rates_total-2-rangeN-begin,若 begin>0 则通过 PlotIndexSetInteger 把绘图起点推到 rangeN+begin,避免前面空段出野值。 主循环里把 price 数组倒序(ArraySetAsSeries(price,true)),对每个 i 截取 rangeN 根价格进 PriceInt,调 KendallCalc 写缓冲。prev_calculated 正常时只补算新增 bar,limit=rates_total-prev_calculated,这是 MT5 指标不掉帧的标准写法。 KendallCalc 造一个严格递减序列 Y(N,N-1,…,1)当作时间秩,再调 MathCorrelationKendall(Ranks,Y,t) 返回 t。也就是说,它测的是‘价格高低顺序’与‘时间先后顺序’的一致概率,输出范围 -1 到 1,贴近 1 可能表示单调上行倾向。 开 MT5 把 rangeN 设 20 挂 EURUSD 小时图,肉眼比对指标拐头与波段的错位,能直接验证滞后量。

MQL5 / C++
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">int begin,
                 class="kw">const class="type">class="kw">double &price[]
                 )
  {
   if(rates_total<rangeN+begin)
      class="kw">return(class="num">0);
   class="type">int limit;
   if(prev_calculated>rates_total || prev_calculated<=class="num">0)
     {
      limit=rates_total-class="num">2-rangeN-begin;
      if(begin>class="num">0)
         PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,rangeN+begin);
     }
   else
      limit=rates_total-prev_calculated;
   ArraySetAsSeries(price,true);
   for(class="type">int i=class="num">0; i<=limit; i++)
     {
      for(class="type">int k=class="num">0; k<rangeN; k++)
         PriceInt[k]=price[k+i];
      ExtLineBuffer[i]=KendallCalc(PriceInt,rangeN);
     }
   class="kw">return(rates_total);
  }

class="type">class="kw">double KendallCalc(class="type">class="kw">double &Ranks[],class="type">int N)
  {
   class="type">class="kw">double Y[],t;
   ArrayResize(Y,N);
   class="type">int n=N;
   for(class="type">int i=class="num">0; i<N; i++)
     {
      Y[i]=n;
      n--;
     }
   MathCorrelationKendall(Ranks,Y,t);
   class="kw">return (t);
  }

Fechner 相关系数的循环与边界处理

这段逻辑先卡住数据量底线:当可用 K 线数小于 rangeN+begin 时直接返回 0,避免数组越界。首次计算(prev_calculated<=0 或异常)会把 limit 推到 rates_total-2-rangeN-begin,若 begin>0 则通过 PlotIndexSetInteger 告诉图表从第 rangeN+begin 根 K 线才开始画线。 后续刷新只处理新增部分,limit 等于 rates_total-prev_calculated,配合 ArraySetAsSeries(price,true) 把价格数组倒序,使得 i=0 对应最新一根 K 线。内层把 price[k+i] 拷进 PriceInt,再调 FechnerCalc 得到 ExtLineBuffer[i]。 FechnerCalc 本身不碰原始价格,而是拿排序名次 Ranks 和理想序列 Y(N、N-1…1)比符号。先求两者均值 mx、my,逐点打 markx/marky 为 ±1,同向记 Na、反向记 Nb,最终 res=(Na-Nb)/(Na+Nb),值域落在 -1 到 1。 在 MT5 里把 rangeN 设为 10 跑一遍,极限情况下若 10 个点全反向,缓冲区会写出 -1.0;全同向则 1.0。外汇与贵金属杠杆高、滑点随机,该系数只反映历史片段的同向概率,实盘前务必用策略测试器看不同品种的稳定性。

MQL5 / C++
  if(rates_total<rangeN+begin)
      class="kw">return(class="num">0);
  class="type">int limit;
  if(prev_calculated>rates_total || prev_calculated<=class="num">0)
     {
       limit=rates_total-class="num">2-rangeN-begin;
       if(begin>class="num">0)
         PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,rangeN+begin);
     }
  else
      limit=rates_total-prev_calculated;
  ArraySetAsSeries(price,true);
  for(class="type">int i=class="num">0; i<=limit; i++)
     {
       for(class="type">int k=class="num">0; k<rangeN; k++)
         PriceInt[k]=price[k+i];
       ExtLineBuffer[i]=FechnerCalc(PriceInt,rangeN);
     }
  class="kw">return(rates_total);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 计算 Fechner 相关系数                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double FechnerCalc(class="type">class="kw">double &Ranks[],class="type">int N)
  {
   class="type">class="kw">double Y[],res,mx,my,sum=class="num">0.0,markx[],marky[];
   class="type">class="kw">double Na=class="num">0.0,Nb=class="num">0.0;
   ArrayResize(Y,N);
   ArrayResize(markx,N);
   ArrayResize(marky,N);
   class="type">int n=N;
   for(class="type">int i=class="num">0; i<N; i++)
     {
       Y[i]=n;
       n--;
     }
   mx=Average(Y);
   my=Average(Ranks);
   for(class="type">int j=class="num">0; j<N; j++)
     {
       markx[j]=(Y[j]>mx)?class="num">1:-class="num">1;
       marky[j]=(Ranks[j]>my)?class="num">1:-class="num">1;
       if(markx[j]==marky[j])
         Na++;
       else
         Nb++;
     }
   res=(Na-Nb)/(Na+Nb);
   class="kw">return (res);
  }

◍ 用相关系数振荡指标抓突破

做相关性策略,先得看清指标怎么算、滞后在哪。几乎所有相关性指标都不建议拉大周期,周期一大就滞后;价格急转时,它们解释的是已完成的反向波动,弱相关会直接暴露出来。 指标盯的不是当下价格,而是一段窗口内多值的总和,所以当前相关系数别当绝对估值,要看动态。用当前值与前期值做的振荡指标,才能识别突破倾向。 策略只做两件事:系数下降模式,正系数跌破0.3卖、负系数涨破+0.3买;系数增长模式,落负区破位卖、涨破对称正位买。截图里卖位0.3、买位0.3对称摆着,向下破卖、向上破买。 EA 里把计算方法(Pearson/Spearman/Kendall/Fechner)、策略类型(降/增)、关键水平、时间框架做成输入。初始化要卡一道:水平绝对值必须落在0到1之间,否则逻辑无意义。 外汇与贵金属杠杆高,相关系数突破只是概率信号,急反转中失效可能偏大,实盘前用 MT5 策略测试器跑一遍再上。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 运行模式的枚举                                                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum Strategy_type
  {
   DECREASE = class="num">1,         class=class="str">"cmt">//On Decrease
   INCREASE                class=class="str">"cmt">//On Increase
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 相关性计算方法的枚举                                                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum Corr_method
  {
   PEARSON = class="num">1,          class=class="str">"cmt">//Pearson
   SPEARMAN,             class=class="str">"cmt">//Spearman
   KENDALL,              class=class="str">"cmt">//Kendall
   FECHNER               class=class="str">"cmt">//Fechner
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA 交易输入参数                                                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
input    class="type">class="kw">string            Inp_EaComment="Correlation Strategy";             class=class="str">"cmt">//EA 注释
input    class="type">class="kw">double            Inp_Lot=class="num">0.01;                                    class=class="str">"cmt">//手数
input    MarginMode        Inp_MMode=LOT;                                   class=class="str">"cmt">//资金管理
class=class="str">"cmt">//--- 相关性的计算
input    Corr_method       Inp_Corr_method=class="num">1;                               class=class="str">"cmt">//相关性方法
input    Strategy_type     Inp_Strategy_type=class="num">1;                             class=class="str">"cmt">//策略类型
class=class="str">"cmt">//--- EA 参数
input    class="type">class="kw">string            Inp_Str_label="===EA parameters===";             class=class="str">"cmt">//标签
input    class="type">int               Inp_MagicNum=class="num">1111;                               class=class="str">"cmt">//幻数

「把相关性指标接进EA的初始化与 tick 逻辑」

下面这段初始化代码把外部相关性指标挂进 EA,并顺手做了参数合法性校验。关键水平 KeyLevel 被限制在 0~1 之间,超出就直接 INIT_FAILED,避免后续拿非法阈值去算信号。 相关系数方法用 switch 分发:1=Pearson、2=Spearman、3=Kendall、4=Fechner,分别对应 Correlation 目录下的自定义指标。改方法只需换 Inp_Corr_method 数字,不用动底层调用。 iCustom 拿句柄时若返回 INVALID_HANDLE,会打印错误码并终止初始化。实盘里若指标路径不符(例如没把 Corr 文件放进 MQL5/Indicators/Correlation),这里就会卡死,开 MT5 前先确认目录。 OnTick 里先调 GetIndValue 取数,再用 Trade.IsOpenedByMagic 判断同魔术码持仓。无持仓且出买入信号才去开单——这套结构让你复制时只需补信号函数,风控框架已经现成。外汇与贵金属杠杆高,参数 40/60 点止损止盈仅作示例,需按品种波动重设。

MQL5 / C++
input   class="type">int                Inp_StopLoss=class="num">40;                                     class=class="str">"cmt">//止损(点数)
input   class="type">int                Inp_TakeProfit=class="num">60;                                  class=class="str">"cmt">//获利(点数)
class=class="str">"cmt">//--- 指标参数 
input   class="type">int                Inp_RangeN=class="num">10;                                      class=class="str">"cmt">//范围计算
input   class="type">class="kw">double             Inp_KeyLevel=class="num">0.2;                                   class=class="str">"cmt">//关键水平    
input   ENUM_TIMEFRAMES    Inp_Timeframe=PERIOD_CURRENT;                       class=class="str">"cmt">//工作时间框架
class=class="str">"cmt">//--- 检查关键水平的正确性
   if(Inp_KeyLevel>class="num">1 || Inp_KeyLevel<class="num">0)
   {
      Print(Inp_EaComment,": 错误的关键水平!");
      class="kw">return(INIT_FAILED);
   }
class=class="str">"cmt">//--- 
   class="kw">switch(Inp_Corr_method)
   {
      case class="num">1:
         ind_type="Correlation\\PearsonCorrelation";
         class="kw">break;
      case class="num">2:
         ind_type="Correlation\\SpearmanCorrelation";
         class="kw">break;
      case class="num">3:
         ind_type="Correlation\\KendallCorrelation";
         class="kw">break;
      case class="num">4:
         ind_type="Correlation\\FechnerCorrelation";
         class="kw">break;
      class="kw">default:
         class="kw">break;
   }
class=class="str">"cmt">//--- 取得指标句柄 
   InpInd_Handle=iCustom(Symbol(),Inp_Timeframe,ind_type,Inp_RangeN);
   if(InpInd_Handle==INVALID_HANDLE)
   {
      Print(Inp_EaComment,": Failed to get indicator handle");
      Print("Handle = ",InpInd_Handle,"  error = ",GetLastError());
      class="kw">return(INIT_FAILED);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易分时函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 取得用于计算的数据
   if(!GetIndValue())
      class="kw">return;
   if(!Trade.IsOpenedByMagic(Inp_MagicNum))
   {
      class=class="str">"cmt">//--- 如果有买入信号,开启订单
把依赖强度交给小布算
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时相关系数面板,你只需在阈值越线时做决策,不用自己写指标拉历史数据。

常见问题

常见做法把 0.75 以上视为强相关,但外汇贵金属波动受宏观事件干扰,阈值应随品种和周期回测调整,仅作概率参考。
可先对变量做散点图观察函数形态,再用秩相关等非线性度量,比单纯皮尔逊系数更不容易漏掉弯曲依赖。
可以,品种页的 AIGC 面板支持选双标的输出滚动相关系数,省去手动导出报价再算的步骤。
相关性看同步方向,协整看偏离回归;跨期价差交易更依赖协整,单纯趋势共振用相关性就够了,两者都只是概率工具。