相关性在交易中的实际应用(基础篇)
◍ 相关性分析在 MT5 里的落点
这篇系列开篇先理清一件事:相关性不是图表装饰,而是 MT5 里能直接量化、能写进 EA 的逻辑输入。作者 Alexander Fedosov 在 2019 年 3 月发布的原文,截至统计时点有 5,764 次查看、42 条讨论,说明基层交易员对「品种间统计关系」的需求一直很实。 原文给出的章节骨架是:概念 → 类型 → 指标实现 → 交易系统 → 回测 → 结果 → 结论。也就是说,它不走「相关性很重要」的空谈路线,而是从 Pearson 系数一路推到可挂机的系统。外汇与贵金属跨品种对冲的高风险在于:相关性会 regime shift,危机里黄金和美元指数可能瞬间同向,历史系数不能直接当仓位依据。 对你而言,开 MT5 后第一件能验证的事,就是调出两个品种的标准差归一化序列,看它们滚动 20 根 K 线的协方差符号。这一步跑通,后面指标实现章节的代码才有意义。
「从价格运动特征看概率工具的价值」
价格在任何市场里的移动都不是孤立的,它自带方向、速度和加速度三个属性,外汇与贵金属的走势同样服从这套描述。一段行情总有启动、加速惯性、衰减到结束的过程,能尽早识别启动并卡准衰减边界,策略才站得住脚。 但必须直说:没有任何方法能用绝对概率锁死进出场点,这行本质是在有利机会和胜率之间做取舍。外汇与贵金属杠杆高、跳空频繁,把‘一定’当口头禅的人往往先爆仓。 所以这一篇借用的抓手是概率论里的相关性(correlation),把它直接架到金融时间序列上用。它不是预测神器,只是帮你在杂乱报价里量化两段运动是否同步、背离,从而提高识别‘真启动’的概率。
用相关系数给趋势强度称重
相关性描述的是两个随机变量之间的统计依存:一个量发生系统性变动时,另一个量是否跟着出现规则变化。数学上用相关系数度量,区间固定在 -1 到 +1。越靠近 +1 是正相关(甲涨乙也涨),越靠近 -1 是负相关(甲涨乙跌),靠近 0 则基本无线性依存。 在 MT5 里看一段跌势,肉眼能认出多数收盘价低于开盘、且逐根走低,但中间常有像第 4、6、9 根那样的上冲尝试。这时光靠看图说不清趋势靠不靠谱。把相关系数拉出来,当前值直接反映这波运动的强度;盯住一个阈值(比如 0.75)能判断持续性——若连续 3~4 根以上都稳在阈值上方,趋势延续的概率更高,外汇与贵金属杠杆品种须警惕假突破反转风险。 相关系数本身不预言方向,只量化已有数据的依赖程度。你可以直接开 MT5 加载一段行情,用下方代码算滚动相关,验证阈值停留时长与肉眼趋势是否吻合。
◍ 三种相关系数在 EURUSD 样本上的实测差异
拿一组真实的 10 根 EURUSD 日线收盘价做验证:价格从 1.23406 一路滑到 1.20995,烛形编号 1 到 10。这套数据能直接拷进 Excel 或 MT5 自定义脚本里复算,看不同相关系数怎么给出结论。 Pearson 系数按绝对值算线性依赖。先求均值——价格均值 1.22020、编号均值 5.5,再算偏差乘积与平方偏差。分母平方偏差乘积 0.19149,分子价格偏差乘编号偏差 -0.17928,得出 Pearson = -0.93623,强线性负相关。 Spearman 改用排名而非数值:最低价排 1,最高价排 10,编号同理。等级差平方和 320 代入公式,得到 -0.93939,与 Pearson 几乎一致。但注意,价格等级差并不均匀(千分之几跳动),而编号等级可比,这方法在价格跳变微小时会掩盖非线性结构。 Kendall 也用排名,但数「匹配对 P」和「反转对 Q」。按收盘价等级重排后,P 与 Q 差为 -37,系数 -0.82。绝对值比前两个小,说明它对单调但非严格线性的关系更挑剔。Fechner 更简单:只比偏离均值方向的符号,本例 Na=1、Nb=9,系数 -0.8。四种算法都指向同一件事——该段样本里价格随编号上升而走低,外汇与贵金属这类高杠杆品种中,这种短样本相关不代表趋势延续,实盘需扩大样本并警惕拟合。
「四种相关指标在 MT5 里的落地差异」
Pearson 系数按大公式拆成分子、分母两段来算,Spearman 直接借已有的排名处理方案,Kendall 走 MT5 内置的数理统计库,Fechner 则只数收盘价偏离均值方向是否和时间序列符号一致。图 6 把四种算法都跑在周期 10 上,横向比一眼能看出同一段行情里谁更敏感。 下面这段是 Pearson 分子分母的 MQL5 实现骨架,复制进自定义指标就能编译。Numerator 里先把 Y 填成 N 到 1 的序列当作时间秩,减掉各自均值后做叉乘累加;Denominator 同样减均值但走平方再开根号,返回两序列离差乘积和的根号。 //+------------------------------------------------------------------+
| // | 分子的计算 |
|---|
//+------------------------------------------------------------------+ double Numerator(double &Ranks[],int N) { //---- double Y[],dx[],dy[],mx=0.0,my=0.0,sum=0.0,sm=0.0; ArrayResize(Y,N); ArrayResize(dx,N); ArrayResize(dy,N); int n=N; for(int i=0; i<N; i++) { Y[i]=n; n--; } mx=Average(Y); my=Average(Ranks); for(int j=0;j<N;j++) { dx[j]=Y[j]-mx; dy[j]=Ranks[j]-my; sm+=dx[j]*dy[j]; } return sm; } //+------------------------------------------------------------------+
| // | 分母的计算 |
|---|
//+------------------------------------------------------------------+ double Denominator(double &Ranks[],int N) { //---- double Y[],dx2[],dy2[],mx=0.0,my=0.0,sum=0.0,smx2=0.0,smy2=0.0; ArrayResize(Y,N); ArrayResize(dx2,N); ArrayResize(dy2,N); int n=N; for(int i=0; i<N; i++) { Y[i]=n; n--; } mx=Average(Y); my=Average(Ranks); for(int j=0;j<N;j++) { dx2[j]=MathPow(Y[j]-mx,2); dy2[j]=MathPow(Ranks[j]-my,2); smx2+=dx2[j]; smy2+=dy2[j]; } return(MathSqrt(smx2*smy2)); } //+------------------------------------------------------------------+
| // | 自定义指标迭代函数 |
|---|
//+------------------------------------------------------------------+ int OnCalculate(const int rates_total, // 当前分时历史中的柱数 const int prev_calculated,// 前一次调用时计算的柱数 const int begin, // 柱开始计数的索引 const double &price[] ) { if(rates_total<rangeN+begin) return(0); int limit;
| if(prev_calculated>rates_total | prev_calculated<=0) |
|---|
{ limit=rates_total-2-rangeN-begin; if(begin>0) 把 rangeN 设成 10 挂上 EURUSD 的 H1 收盘价,能直接复现图 6 里 Pearson 那条线。外汇和贵金属杠杆高,相关值只是概率层面的参考,别拿它当单边依据。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 分子的计算 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double Numerator(class="type">class="kw">double &Ranks[],class="type">int N) { class=class="str">"cmt">//---- class="type">class="kw">double Y[],dx[],dy[],mx=class="num">0.0,my=class="num">0.0,sum=class="num">0.0,sm=class="num">0.0; ArrayResize(Y,N); ArrayResize(dx,N); ArrayResize(dy,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++) { dx[j]=Y[j]-mx; dy[j]=Ranks[j]-my; sm+=dx[j]*dy[j]; } class="kw">return sm; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 分母的计算 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double Denominator(class="type">class="kw">double &Ranks[],class="type">int N) { class=class="str">"cmt">//---- class="type">class="kw">double Y[],dx2[],dy2[],mx=class="num">0.0,my=class="num">0.0,sum=class="num">0.0,smx2=class="num">0.0,smy2=class="num">0.0; ArrayResize(Y,N); ArrayResize(dx2,N); ArrayResize(dy2,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++) { dx2[j]=MathPow(Y[j]-mx,class="num">2); dy2[j]=MathPow(Ranks[j]-my,class="num">2); smx2+=dx2[j]; smy2+=dy2[j]; } class="kw">return(MathSqrt(smx2*smy2)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class=class="str">"cmt">// 当前分时历史中的柱数 class="kw">const class="type">int prev_calculated,class=class="str">"cmt">// 前一次调用时计算的柱数 class="kw">const class="type">int begin, class=class="str">"cmt">// 柱开始计数的索引 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)