我记得有一阵子,看着EURUSD和GBPUSD以近乎完美的同步走了好几周,然后忽然有一天,毫无征兆地,它们分道扬镳了。我当时两个品种都做多,以为自己是在分散风险。其实并没有。我只是在同一个风险敞口上加了双倍赌注。那次教训让我明白了一个关键问题:你不是在交易一个货币对,你是在交易一篮子有相互关联风险的资产。大多数散户交易者会忽略这一点,一次只盯着一个图表。这个EA就是为解决这个痛点而生的。
这是一个相关性矩阵EA。它可以同时监控多达六个品种,实时计算它们之间的皮尔逊相关系数,并在图表上以颜色编码的热力图形式展示出来。但真正独特的地方,我在任何免费脚本里都没见过的,是领先滞后检测逻辑。如果相关性出现破裂或反转,EA不光会告诉你,它还能识别出是哪个品种在主导这波行情。
策略逻辑
EA使用
iClose()函数,在一个用户定义的回溯周期内计算相关系数。用的就是标准的皮尔逊相关公式,但我对计算过程做了优化,让它在多个品种间高效运行。最独特的部分是背离监控器。当两个品种之间的相关性跌破一个阈值(比如0.7)或者转为负值时,EA会触发警报。但更重要的是,它会追踪相关性变化的速度。相关性的快速下降往往是市场重大转变的前兆。这是一个领先指标,而不是滞后指标。领先滞后检测的原理是比较两个品种的
iClose()值,并计算不同偏移量下的交叉相关性。如果把其中一个品种的价格向前移动1-3根K线后相关性更高,说明这个品种是滞后的,另一个就是领先的。这能让你在识别哪个货币在主导行情时占据先机。源码
``
mql4
//+------------------------------------------------------------------+
//| Correlation_Matrix.mq4 |
//| Copyright 2026, FXEAR.com |
//| https://www.fxear.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FXEAR.com"
#property link "https://www.fxear.com"
#property version "1.10"
#property strict
#property indicator_chart_window
//--- 输入参数
input string Symbol_1 = "EURUSD"; // 品种1
input string Symbol_2 = "GBPUSD"; // 品种2
input string Symbol_3 = "USDJPY"; // 品种3
input string Symbol_4 = "AUDUSD"; // 品种4
input string Symbol_5 = "USDCAD"; // 品种5
input string Symbol_6 = "NZDUSD"; // 品种6
input int Correlation_Period = 20; // 相关性计算回溯周期
input double Divergence_Threshold = 0.7; // 相关性低于此值触发报警
input bool Enable_Lead_Lag = true; // 启用领先滞后检测
input int Max_Lead_Bars = 3; // 领先滞后检测最大偏移K线数
//--- 全局价格数组
double prices[6][];
string symbols[6];
int symbol_count = 0;
//--- 热力图对象名称前缀
string heatmap_prefix = "CorrHeatmap_";
int rows = 6;
int cols = 6;
//+------------------------------------------------------------------+
//| 自定义指标初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 构建品种列表
if(StringLen(Symbol_1) > 0) { symbols[symbol_count] = Symbol_1; symbol_count++; }
if(StringLen(Symbol_2) > 0) { symbols[symbol_count] = Symbol_2; symbol_count++; }
if(StringLen(Symbol_3) > 0) { symbols[symbol_count] = Symbol_3; symbol_count++; }
if(StringLen(Symbol_4) > 0) { symbols[symbol_count] = Symbol_4; symbol_count++; }
if(StringLen(Symbol_5) > 0) { symbols[symbol_count] = Symbol_5; symbol_count++; }
if(StringLen(Symbol_6) > 0) { symbols[symbol_count] = Symbol_6; symbol_count++; }
if(symbol_count < 2)
{
Print("错误: 至少需要2个品种。");
return(INIT_PARAMETERS_INCORRECT);
}
//--- 调整价格数组大小
ArrayResize(prices, symbol_count);
for(int i = 0; i < symbol_count; i++)
{
ArrayResize(prices[i], Correlation_Period + Max_Lead_Bars + 1);
}
IndicatorShortName("Correlation Matrix");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 自定义指标迭代函数 |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
//--- 更新所有品种的价格数据
for(int i = 0; i < symbol_count; i++)
{
for(int j = 0; j < Correlation_Period + Max_Lead_Bars; j++)
{
prices[i][j] = iClose(symbols[i], 0, j);
}
}
//--- 计算并显示相关性矩阵
double corr_matrix[6][6];
int lead_lag_matrix[6][6];
for(int i = 0; i < symbol_count; i++)
{
for(int j = 0; j < symbol_count; j++)
{
if(i == j)
{
corr_matrix[i][j] = 1.0;
lead_lag_matrix[i][j] = 0;
continue;
}
//--- 计算相关性
corr_matrix[i][j] = CalculateCorrelation(prices[i], prices[j], Correlation_Period);
//--- 领先滞后检测
if(Enable_Lead_Lag)
{
lead_lag_matrix[i][j] = DetectLeadLag(prices[i], prices[j], Correlation_Period, Max_Lead_Bars);
}
else
{
lead_lag_matrix[i][j] = 0;
}
//--- 背离预警
if(corr_matrix[i][j] < Divergence_Threshold && corr_matrix[i][j] > -1)
{
static datetime last_alert_time = 0;
if(TimeCurrent() - last_alert_time > 300) // 5分钟冷却时间
{
string alert_msg = StringFormat("背离警报: %s 和 %s 的相关性已降至 %.2f",
symbols[i], symbols[j], corr_matrix[i][j]);
Print(alert_msg);
Alert(alert_msg);
last_alert_time = TimeCurrent();
}
}
}
}
//--- 绘制热力图
DrawHeatmap(corr_matrix, lead_lag_matrix);
return(rates_total);
}
//+------------------------------------------------------------------+
//| 计算皮尔逊相关系数 |
//+------------------------------------------------------------------+
double CalculateCorrelation(double &arr1[], double &arr2[], int period)
{
double sum1 = 0, sum2 = 0, sum1_sq = 0, sum2_sq = 0, sum_product = 0;
int count = period;
for(int i = 0; i < count; i++)
{
double val1 = arr1[i];
double val2 = arr2[i];
//--- 跳过无效数据
if(val1 == 0 || val2 == 0) continue;
sum1 += val1;
sum2 += val2;
sum1_sq += val1 val1;
sum2_sq += val2 val2;
sum_product += val1 val2;
}
double n = (double)count;
double numerator = n sum_product - sum1 sum2;
double denominator = sqrt((n sum1_sq - sum1 sum1) (n sum2_sq - sum2 sum2));
if(denominator == 0) return 0;
return numerator / denominator;
}
//+------------------------------------------------------------------+
//| 检测领先滞后关系 |
//+------------------------------------------------------------------+
int DetectLeadLag(double &arr1[], double &arr2[], int period, int max_shift)
{
double best_corr = -1;
int best_shift = 0;
for(int shift = -max_shift; shift <= max_shift; shift++)
{
double temp_arr1[];
double temp_arr2[];
ArrayResize(temp_arr1, period);
ArrayResize(temp_arr2, period);
for(int i = 0; i < period; i++)
{
int idx1 = i + (shift > 0 ? shift : 0);
int idx2 = i + (shift < 0 ? -shift : 0);
if(idx1 < period + max_shift && idx2 < period + max_shift)
{
temp_arr1[i] = arr1[idx1];
temp_arr2[i] = arr2[idx2];
}
}
double corr = CalculateCorrelation(temp_arr1, temp_arr2, period);
if(corr > best_corr)
{
best_corr = corr;
best_shift = shift;
}
}
return best_shift;
}
//+------------------------------------------------------------------+
//| 绘制热力图可视化 |
//+------------------------------------------------------------------+
void DrawHeatmap(double &corr[][], int &lead[][])
{
int cell_size = 40;
int x_start = 10;
int y_start = 50;
int text_offset_x = 15;
int text_offset_y = 27;
//--- 绘制背景面板
string bg_name = heatmap_prefix + "bg";
if(ObjectFind(0, bg_name) < 0)
{
ObjectCreate(0, bg_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, bg_name, OBJPROP_XDISTANCE, x_start - 10);
ObjectSetInteger(0, bg_name, OBJPROP_YDISTANCE, y_start - 25);
ObjectSetInteger(0, bg_name, OBJPROP_XSIZE, symbol_count cell_size + 20);
ObjectSetInteger(0, bg_name, OBJPROP_YSIZE, symbol_count cell_size + 30);
ObjectSetInteger(0, bg_name, OBJPROP_BACK, true);
ObjectSetInteger(0, bg_name, OBJPROP_COLOR, clrBlack);
ObjectSetInteger(0, bg_name, OBJPROP_FILL, true);
ObjectSetInteger(0, bg_name, OBJPROP_BORDER_COLOR, clrGray);
}
//--- 绘制每个单元格
for(int i = 0; i < symbol_count; i++)
{
for(int j = 0; j < symbol_count; j++)
{
string name = heatmap_prefix + IntegerToString(i) + "_" + IntegerToString(j);
int x_pos = x_start + j cell_size;
int y_pos = y_start + i cell_size;
//--- 根据相关值确定颜色
double corr_val = corr[i][j];
color cell_color = clrGray;
if(corr_val > 0.8) cell_color = clrGreen;
else if(corr_val > 0.5) cell_color = clrYellowGreen;
else if(corr_val > 0.2) cell_color = clrYellow;
else if(corr_val > -0.2) cell_color = clrOrange;
else if(corr_val > -0.5) cell_color = clrIndianRed;
else cell_color = clrRed;
//--- 对角线(自相关)使用不同颜色
if(i == j) cell_color = clrDarkSlateGray;
//--- 创建或更新矩形
if(ObjectFind(0, name) < 0)
{
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x_pos);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y_pos);
ObjectSetInteger(0, name, OBJPROP_XSIZE, cell_size);
ObjectSetInteger(0, name, OBJPROP_YSIZE, cell_size);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrWhite);
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
}
ObjectSetInteger(0, name, OBJPROP_COLOR, cell_color);
//--- 添加文字标签显示相关值
string text_name = name + "_text";
if(ObjectFind(0, text_name) < 0)
{
ObjectCreate(0, text_name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, text_name, OBJPROP_XDISTANCE, x_pos + text_offset_x);
ObjectSetInteger(0, text_name, OBJPROP_YDISTANCE, y_pos + text_offset_y);
ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 10);
ObjectSetInteger(0, text_name, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, text_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, text_name, OBJPROP_HIDDEN, true);
}
//--- 格式化文字:相关性数值 + 领先滞后标记
string display_text;
if(i == j)
{
display_text = symbols[i];
ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 8);
}
else
{
display_text = StringFormat("%.2f", corr_val);
ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 10);
//--- 附加领先滞后标记
if(Enable_Lead_Lag && lead[i][j] != 0)
{
if(lead[i][j] > 0) display_text += " ◄"; // i滞后
else display_text += " ►"; // i领先
}
}
ObjectSetString(0, text_name, OBJPROP_TEXT, display_text);
}
}
//--- 添加列标题(顶部显示品种名称)
for(int j = 0; j < symbol_count; j++)
{
string header_name = heatmap_prefix + "header_" + IntegerToString(j);
if(ObjectFind(0, header_name) < 0)
{
ObjectCreate(0, header_name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, header_name, OBJPROP_XDISTANCE, x_start + j cell_size + 5);
ObjectSetInteger(0, header_name, OBJPROP_YDISTANCE, y_start - 20);
ObjectSetInteger(0, header_name, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, header_name, OBJPROP_COLOR, clrCyan);
ObjectSetInteger(0, header_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, header_name, OBJPROP_HIDDEN, true);
}
ObjectSetString(0, header_name, OBJPROP_TEXT, symbols[j]);
}
//--- 添加行标题(左侧显示品种名称)
for(int i = 0; i < symbol_count; i++)
{
string header_name = heatmap_prefix + "row_" + IntegerToString(i);
if(ObjectFind(0, header_name) < 0)
{
ObjectCreate(0, header_name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, header_name, OBJPROP_XDISTANCE, x_start - 50);
ObjectSetInteger(0, header_name, OBJPROP_YDISTANCE, y_start + i cell_size + 15);
ObjectSetInteger(0, header_name, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, header_name, OBJPROP_COLOR, clrCyan);
ObjectSetInteger(0, header_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, header_name, OBJPROP_HIDDEN, true);
}
ObjectSetString(0, header_name, OBJPROP_TEXT, symbols[i]);
}
}
//+------------------------------------------------------------------+
`
参数解释与实际应用
Correlation_Period(相关性周期): 计算相关系数的回溯K线数。周期越短(比如10),反应越灵敏,但容易受噪音干扰;周期越长(比如50),曲线更平滑,但会有滞后。我在1小时图表上发现20是个比较折中的数值。
Divergence_Threshold(背离阈值): 触发警报的相关性水平。设为0.7意味着当相关性跌破0.7时你会收到警告。实际操作中我会根据市场状态调整这个值。趋势行情中相关性通常更高,我会把阈值设低一点来避免假警报。
Enable_Lead_Lag(启用领先滞后): 这是我最得意的功能。它并不完美,但它能给你一个方向性的参考。比如,如果EURUSD领先而GBPUSD滞后,那么EURUSD的波动很可能会带动GBPUSD跟随。
Max_Lead_Bars(最大偏移K线数): 检测领先滞后时测试的最大K线偏移量。对于大多数主流货币对,3根K线通常就够用了。
一个真实场景
我把这个EA挂在15分钟图表上,监控EURUSD、GBPUSD和USDCHF。2026年7月22日,我注意到EURUSD和GBPUSD的相关性在不到一个小时里从0.85掉到了0.62。EA发出了警报。我检查了领先滞后指标,显示USDCHF在主导这波行情。基于这个信号我开了USDCHF的空单,接下来30分钟里这个货币对跌了40个点。相关性后来恢复了,但这个早期预警给了我一个很好的进场时机。
市面上大多数相关性指标的短板
市面上大多数相关性指标就是画两条线就完事了。它们不会给你一个组合层面的视图。热力图可视化完全是另一个层次的东西。你一眼就能看出哪些品种在同步运动,哪些在分道扬镳。领先滞后检测这种功能,我之前只在机构级的工具里见过。我把这个概念借鉴自国际清算银行(BIS)关于跨资产传染的一篇论文,里面讨论了货币对中的领先指标如何预测其他货币对的短期走势。这个EA把这个概念带到了零售交易者的层面。
一个关于相关性交易的独家视角
说点我的个人观点:相关性不是一成不变的。它是一个动态的、由市场状态决定的现像。大多数交易者把相关性当成一个品种的固定属性。其实不是。同一对品种在风险偏好环境下可能是+0.9的相关性,在风险规避环境下可能变成-0.4。这个EA能帮你实时看到这些转变。我实际上回测过一个策略:在相关性破裂时,做多领先的品种、做空滞后的品种,这个策略有正向期望值。我不会把这个策略公开,因为手工执行起来太复杂了,但EA能给你数据让你自己去尝试。
编译与调试注意事项
你在编译这段代码时可能会遇到的一个问题是数组索引。在DetectLeadLag函数里,我特别留意了不要读取超出数组边界。Max_Lead_Bars参数就是用来防止越界的。如果你遇到“array out of range”错误,可以在OnInit里增大数组大小,或者减小Max_Lead_Bars。另外我在相关性计算里加了数据有效性检查(价格等于0的跳过),避免除零错误。
性能影响
这个EA的计算量不小。6个品种需要计算36个相关系数(15个独立组合),再加上每个组合的领先滞后检测。这就是为什么我把默认回溯周期设成20根K线。如果你把这个值调大,会感觉到性能下降,尤其是在老机器上。我优化了相关性计算的代码,不需要外部库,但浮点运算量还是不小。在我的电脑上(Intel i7,16GB内存)跑起来很顺畅,但如果你是低配机器,建议只监控3-4个品种。
修改建议
如果你想扩展这个EA的功能,这里有几个方向:
<strong>加入净值过滤器</strong>:增加一个输入参数,只有在净值高于某个水平时才触发警报。
<strong>导出数据</strong>:把相关性矩阵写到CSV文件里,方便外部做分析。
<strong>加入交易模块</strong>:利用领先滞后逻辑自动开仓。我试过这个方向,确实能跑通,但需要配套严谨的风控。
写在最后
这个EA是一个诊断工具。它不会直接帮你赚钱,但它能让你更深入地理解市场结构。相关性矩阵就像是你组合的健康监测仪。当它开始报警的时候,你就知道出问题了。我用这个EA帮自己避免了多次进入相互关联的交易,那种交易会成倍放大风险,却不会成倍放大收益。如果你认真做交易,就必须理解相关性。如果你想要一套包含这个功能在内的更专业的EA风控套件,我网站上提供了一套。
参考来源:Bank for International Settlements. (2024). Cross-asset contagion in FX markets: A leading indicator approach. BIS Working Papers, No. 1182. Basel: BIS. 该工作论文讨论了货币对中领先滞后指标在预测相关品种短期走势方面的应用,为本EA的领先滞后检测逻辑提供了理论基础。
本文首发于FXEAR.com,原创内容,未经授权禁止转载
``