价格行为分析工具包开发(第 22 部分):相关性仪表盘(基础篇)
「相关性仪表盘能替你盯住品种联动」
做外汇或贵金属交易,单看一个品种容易掉进盲区。多个货币对或 XAUUSD 与美指之间常有同步或背离,手动比对费时且滞后,MT5 自带窗口联动也只够肉眼扫。 相关性仪表盘的思路,是把一组自选品种的滚动相关系数集中算出来,用颜色块直观标出强正相关、强负相关和脱钩区间。外汇与贵金属杠杆高、波动突兀,这类联动断裂往往先于单边行情,属于高风险环境下的预警信号,只能当作概率参考。 在 MT5 里验证很简单:把仪表盘加载到图表,喂入你想监控的 4~6 个品种,周期设成 H1 或 D1,观察颜色变化是否比价格突破更早出现。
相关性不是因果,却是仓位的第一道闸门
相关性衡量两个变量如何相对变动:同涨同跌为正相关,反向运动为负相关。它只描述相互关系,不证明谁驱动谁,这一点在拿它做仓位决策时不能忘。 外汇与贵金属属高风险品种,相关系数算错或误读,可能放大回撤而非降低风险。 EURUSD 与 GBPUSD 在历史样本里倾向同步:EURUSD 上行时 GBPUSD 多跟随上行,属强正相关;EURUSD 与 USDCHF 则倾向反向,EURUSD 涨而 USDCHF 跌,属强负相关。若两者既不同步也不对立,即视为不相关或独立运行。 直接在 MT5 里跑一个自定义 EA 做实时仪表盘,比手动翻图表更快:选好时间范围,它能列出货币、大宗商品等标的相关性系数,用来避开对相关交易过度敞口、找对冲或配对机会、察觉相关性结构突变。
◍ 用相关系数矩阵看清货币对联动
相关性仪表盘类 EA 会持续抓取列表内每个货币对最近 N 根收盘价,转成简单收益率后逐对算皮尔逊 r,再以热力图矩阵铺在图表上:彩色格子直接标出强度与正负号,并自动归类成强正相关、中性、不相关、中等负相关或强负相关。阈值被突破时弹报警,同时打印一句操作释义——对冲、确认、分散或价差,省去你肉眼比对的功夫。 实战里几个参考点值得记:EURUSD 与 GBPUSD 的 r 约 0.85,意味着欧美涨 1% 时,磅美平均也跟涨约 0.85%,两者宜视作一类,别反手持有相反头寸。EURUSD 对 USDCHF 的 r 约 –0.92,欧美涨 1% 瑞郎通常跌 0.92%,跨市场价差(买欧美卖瑞郎)可中和方向敞口。AUDUSD 与 USDJPY 的 r 仅 0.10,近似独立,是组合分散的候选。 外汇与贵金属杠杆高、波动剧烈,相关系数随时变,上述数值仅代表某段样本,不能当恒定规律。下面这段 MQL5 是算 r 的核心,复制进 MT5 把 LookbackBars 和 TimeFrame 调一调就能跑: //+------------------------------------------------------------------+
| // | Pearson correlation |
|---|
//+------------------------------------------------------------------+ double CalcCorrelation(const string s1, const string s2) { double a1[], a2[]; // Fetch closing prices
| if(CopyClose(s1, TimeFrame, 0, LookbackBars, a1) < LookbackBars |
|---|
CopyClose(s2, TimeFrame, 0, LookbackBars, a2) < LookbackBars) return(0); int n = LookbackBars - 1; double r1[], r2[]; ArrayResize(r1, n); ArrayResize(r2, n); // Compute simple returns for(int k = 1; k < LookbackBars; k++) { r1[k-1] = (a1[k] - a1[k-1]) / a1[k-1]; r2[k-1] = (a2[k] - a2[k-1]) / a2[k-1]; } // Calculate means double m1 = AverageArray(r1); double m2 = AverageArray(r2); // Build covariance numerator and variances double num = 0, d1 = 0, d2 = 0; for(int i = 0; i < n; i++) { double da = r1[i] - m1; double db = r2[i] - m2; num += da * db; // covariance sum d1 += da * da; // variance of series 1 d2 += db * db; // variance of series 2 } // Return r (or 0 if division by zero) return (d1 > 0 && d2 > 0) ? num / MathSqrt(d1 * d2) : 0; } 代码逐行拆解:函数入口拿两个品种名,先用 CopyClose 取最近 LookbackBars 根收盘,不足就返回 0;n 设为样本数减 1,给收益率数组留长度。for 循环算相邻收盘的简单收益率 (a[k]-a[k-1])/a[k-1]。AverageArray 求两列收益率均值 m1、m2。第二个循环里 da、db 是离差,num 累加协方差,d1、d2 各算自身方差。最后用 num / 根号(d1*d2) 得出 r,分母为 0 时保守返回 0。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Pearson correlation | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CalcCorrelation(const class="type">class="kw">string s1, const class="type">class="kw">string s2) { class="type">class="kw">double a1[], a2[]; class=class="str">"cmt">// Fetch closing prices if(CopyClose(s1, TimeFrame, class="num">0, LookbackBars, a1) < LookbackBars || CopyClose(s2, TimeFrame, class="num">0, LookbackBars, a2) < LookbackBars) class="kw">return(class="num">0); class="type">int n = LookbackBars - class="num">1; class="type">class="kw">double r1[], r2[]; ArrayResize(r1, n); ArrayResize(r2, n); class=class="str">"cmt">// Compute simple returns for(class="type">int k = class="num">1; k < LookbackBars; k++) { r1[k-class="num">1] = (a1[k] - a1[k-class="num">1]) / a1[k-class="num">1]; r2[k-class="num">1] = (a2[k] - a2[k-class="num">1]) / a2[k-class="num">1]; } class=class="str">"cmt">// Calculate means class="type">class="kw">double m1 = AverageArray(r1); class="type">class="kw">double m2 = AverageArray(r2); class=class="str">"cmt">// Build covariance numerator and variances class="type">class="kw">double num = class="num">0, d1 = class="num">0, d2 = class="num">0; for(class="type">int i = class="num">0; i < n; i++) { class="type">class="kw">double da = r1[i] - m1; class="type">class="kw">double db = r2[i] - m2; num += da * db; class=class="str">"cmt">// covariance sum d1 += da * da; class=class="str">"cmt">// variance of series class="num">1 d2 += db * db; class=class="str">"cmt">// variance of series class="num">2 } class=class="str">"cmt">// Return r(or class="num">0 if division by zero) class="kw">return (d1 > class="num">0 && d2 > class="num">0) ? num / MathSqrt(d1 * d2) : class="num">0; }
「EA 骨架与相关性统计的实现路径」
这段 MQL5 程序把相关性仪表盘拆成了清晰的层:元数据与库声明、工具函数、输入参数、全局变量、生命周期回调、统计核心和图表渲染。开头用 #property strict 强制严格编译,能在写码阶段拦掉一批隐式类型错误;即便本 EA 只做分析不下单,也还是包含了 Trade.mqh,给以后接交易管理留了口子。 AverageArray 是相关性公式里绕不开的一步——先求序列均值。InterpretCorrelation 把算出的系数按阈值翻成“强正相关”这类人话,RecommendAction 再据此给一句实操点评。输入参数里 InstrumentsList 用逗号分隔品种,TimeFrame 定粒度,LookbackBars 控回溯长度,AlertThreshold 设告警线,UpdateSeconds 管重算频率,推送和邮件分开开关。 全局变量里 Instruments 存解析后的品种名,CorrArray 存两两系数,InstrumentCount 记个数。OnInit 先拆列表、确认至少两个品种,SymbolSelect 拉数据,调 DrawDashboard 画初版矩阵,EventSetTimer 起定时;少品种就 INIT_FAILED 不跑。OnDeinit 杀定时器并 ClearObjects 清图。 OnTimer 是动态引擎:UpdateCorrelations 刷全部系数,遍历唯一对写解释和提议到 Experts 标签,RefreshValues 改热力图颜色,CheckAlerts 超阈值就通知。CalcCorrelation 用 CopyClose 取回看柱数的收盘价,转成逐柱百分比变化后算皮尔逊系数,并防了零变化除零。 原代码注释自己点出一个坑:CheckAlerts 里 CorrArray[i] 的索引意图可能是取 Instruments[0] 与 Instruments[i] 的相关性;而且现在只对超正阈值报警,要抓强负相关得改判绝对值。外汇与贵金属波动剧烈、相关性会突变,这类信号仅作概率参考,实盘前务必在 MT5 用历史数据自测。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Correlation Dashboard.mq5| class=class="str">"cmt">//| Copyright class="num">2025, MetaQuotes Ltd.| class=class="str">"cmt">//| [MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.0" class="macro">#class="kw">property strict class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Utility: average of an array | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double AverageArray(const class="type">class="kw">double &arr[]) { class="type">int sz = ArraySize(arr); if(sz == class="num">0) class="kw">return(class="num">0); class="type">class="kw">double sum = class="num">0; for(class="type">int i = class="num">0; i < sz; i++) sum += arr[i]; class="kw">return(sum / sz); } class=class="str">"cmt">//+------------------------------------------------------------------+