配对交易:基于Z值差异的自动优化算法交易(基础篇)
📘

配对交易:基于Z值差异的自动优化算法交易(基础篇)

第 1/3 篇

用Z值差捕捉货币对背离

配对交易的核心,是找两只历史走势高度协同、价差却暂时拉开的外汇或贵金属标的,等它回归。这套思路在 MT5 上能跑成全自动:先把价差序列标准化成 Z 值,Z 值越偏离 0,两标的的短期背离就越显著。 原文给出的样本里,系统对一个外汇篮子做滚动窗口计算,窗口取 20 根 Bar,Z 值阈值设 ±2 时,触发信号的概率约落在每 100 根 Bar 出 3~5 次,属于低频但可验证的择机。外汇与贵金属杠杆高、跳空频繁,Z 值回归只是统计倾向,不代表必然收敛。 要亲手验,开 MT5 把下面代码丢进 EA 的 OnTick,先打印 Z 值看分布,再决定阈值。实盘前务必用策略测试器跑至少一年 Tick 数据。

MQL5 / C++
class="type">class="kw">double CalcZScore(class="type">class="kw">double &prices[], class="type">int period, class="type">class="kw">double currentSpread)
  {
   class="type">class="kw">double mean = class="num">0, var = class="num">0;
   for(class="type">int i=class="num">0;i<period;i++) mean += prices[i];
   mean /= period;                      class=class="str">"cmt">// 先求价差的period周期均值
   for(class="type">int i=class="num">0;i<period;i++) var += (prices[i]-mean)*(prices[i]-mean);
   var /= period;                       class=class="str">"cmt">// 再求方差
   class="type">class="kw">double std = MathSqrt(var);          class=class="str">"cmt">// 标准差
   if(std==class="num">0) class="kw">return class="num">0;
   class="kw">return (currentSpread - mean)/std;   class=class="str">"cmt">// 当前价差减均值除以标准差,得Z值
  }

「配对交易不再是机构专利」

不少交易者把精力耗在叠指标、找圣杯上,但真正能跑通的逻辑常常来自最朴素的统计关系。配对交易就是一例:它靠两只高相关资产的价格偏离回归来获利,本质是对均值回复的统计押注。 该思路上世纪 80 年代由华尔街量化员提出,早先只在大对冲基金内部流转。MT4/MT5 这类终端把历史数据与下单接口放开后,个人盘户才有了实盘落地的可能。 我们接下来拆解的这套 EA,把经典配对框架接上了自适应优化:它不只抓相关资产对的短期价差异常,也会在协整结构断裂时重估参数。外汇与贵金属价差回归带有杠杆与跳空风险,任何信号都只是概率优势,不是确定性收益。

◍ 用Z分数抓货币对的均值回归窗口

配对交易本质是市场中性玩法:找两个高相关品种,等它们的价格关系跑出历史常态区间,再赌回归。对外汇来说这很顺手,EURUSD 和 GBPUSD 因地缘与经济绑定,长期高度正相关,但阶段性谁涨得快谁涨得慢,比值就会拉开。 统计上要盯两件事:相关性看联动紧不紧,平稳性看价格关系会不会漂走不回来。只有平稳的价差才谈得上“偏离后必回均值”,否则就是单边趋势陷阱。货币对之间多数满足弱平稳,所以适合拿来做这种策略。 具体落地:EURUSD 比 GBPUSD 涨猛了,二者比值上翘;比值显著偏离均值时,空被高估的、多被低估的,两头一抵净敞口接近零。大盘涨跌都伤不到中性仓位,高波动期反而容易出偏离机会。 判断“显著偏离”别靠肉眼,用 Z 分数。它算的是当前比值离历史均值几个标准差。正态分布下 Z=+2.0 只发生在约 2.3% 的时间,碰上 ±2.0 这种阈值,回归概率就偏高。外汇和贵金属杠杆高、滑点狠,真跑之前先在 MT5 用历史数据回测阈值胜率,别直接裸上。

MQL5 / C++
Z-score=(当前比值−平均比值)/标准差

Z 值正负与阈值的实操含义

Z-score 的正负号直接给出两个品种的相对贵贱:大于 0 代表品种 1 相对品种 2 被高估,小于 0 则是被低估。这套读数是配对交易里判断「谁贵谁便宜」的第一道筛子。

偏离程度看绝对值:Z<1 属于比值正常区间,不用动;1~2 是中度偏离,可观察;2~3 算显著偏离,往往能当潜在入场参考;超过 3 是极端偏离,均值回归的概率极高,但外汇与贵金属杠杆高、滑点大,极端值也可能持续更久。

计算周期选多长,是策略里最吃经验的旋钮。周期太短,Z 值抖动厉害,假信号频发;周期太长,短期套利窗口基本抓不到。本 EA 的做法是用历史数据回测自动挑周期,让 Z 窗口随当前波动环境动态适配,而不是写死一个参数。

「联动模块与自适配风控的实现骨架」

这套自动化配对交易方案由五个联动模块构成:数据采集与预处理、统计分析、交易决策、自动优化、风险管理。采集层拉取货币对历史与实时报价并做标准化;统计层算价格比值、Z 值及品种间相关系数;决策层定开仓点并管持仓;优化层定期筛参数;风控层算手数并限连续亏损。外汇与贵金属属高杠杆品种,这种模块化切分能让每一环单独调参而不影响其余逻辑。 比值与 Z 值的核心函数先逐根 K 线算 prices1/prices2 的比值,再求均值与标准差,最后转成 Z 值。注意当 stdDev 趋零时直接赋 0,避免除零崩坏——这是实盘跑配对策略最容易忽略的边界。

相关性追踪不只用当前值,而是存一段历史数组,捕捉局部最小值作为额外入场信号。原文逻辑里,当实时相关与历史最小差小于 0.01 且Z超阈值才开仓,意味着你复制时要把 correlationHistory 和 GetMinimumCorrelation() 先补齐,否则条件永远不满足。

默认优化触发设在累计 5000 次 tick 后重跑参数组合,兼顾适应性与过拟合。防过拟合靠四条:部分样本外验证、只调核心参数、Z 周期步长 25 降维度、定期重优。AveragingLotMultiplier 控加仓倍数,相关性降时加仓在对冲与放大收益间切换,但总次数由相关性历史管住。 动态风险用 RiskPercent 随盈亏伸缩:连亏后自动降仓,近期顺手则略放大。连续亏损上限封死在策略不匹配时的暴露。MT5 里把这几段代码挂同一 EA,先拿 EURUSD/GBPUSD 的 M15 跑样本外,看 Z 阈值 2.0 附近触发频率是否过高再调步长。

MQL5 / C++
<span class="keyword">class="type">void</span> CalculateRatioAndZScore()
{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Price ratio calculation</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; CurrentZScorePeriod; i++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(prices2[i] == <span class="number">class="num">0</span>) <span class="keyword">class="kw">continue</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ratio[i] = prices1[i] / prices2[i];
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate the average</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> mean = <span class="number">class="num">0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; CurrentZScorePeriod; i++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mean += ratio[i];
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;mean /= CurrentZScorePeriod;
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculation of standard deviation</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> stdDev = <span class="number">class="num">0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; CurrentZScorePeriod; i++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stdDev += <span class="functions">MathPow</span>(ratio[i] - mean, <span class="number">class="num">2</span>);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;stdDev = <span class="functions">MathSqrt</span>(stdDev / CurrentZScorePeriod);
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate Z-score</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; CurrentZScorePeriod; i++)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(stdDev == <span class="number">class="num">0</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zscore[i] = <span class="number">class="num">0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zscore[i] = (ratio[i] - mean) / stdDev;
&nbsp;&nbsp;&nbsp;&nbsp;}
}
<span class="comment">class=class="str">"cmt">// The logic for opening new positions is to enter when the correlation is at a minimum</span>
<span class="keyword">if</span>(!isPositionOpen)
{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> currentCorrelation = correlationHistory[<span class="number">class="num">0</span>];
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> minCorrelation = GetMinimumCorrelation();
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// If the current correlation is close to the minimum and the Z-score exceeds the threshold</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(<span class="functions">MathAbs</span>(currentCorrelation - minCorrelation) &lt; <span class="number">class="num">0.01</span> &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="functions">MathAbs</span>(currentZScore) &gt;= CurrentEntryThreshold)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate lot size based on risk</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> riskLot = CalculatePositionSize();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(currentZScore &gt; <span class="number">class="num">0</span>) <span class="comment">class=class="str">"cmt">// Symbol1 is overvalued, Symbol2 is undervalued</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{

常见问题

算两货币对价格序列的滚动Z值差,偏离零轴超过±2通常视为背离窗口,可关注回归概率,但外汇高风险,需自设止损。
不是机构专利,把价差转Z分数写进EA,设好阈值就能自动监控;散户用常见平台均可实现,重点在参数别照搬。
可以,小布能按你选的品种计算联动Z分数,突破阈值时推送背离预警,你只管确认要不要进场。
正表示前者相对强、负则弱;阈值常用±1.5到±2.5,太小易假信号,建议用历史波动回测再定。
用ATR或方差做自适配系数,波动大时自动放宽止损倍数,骨架写好后可省去每日手动改参。