功能

配对交易

筛选协整配对、共享现金交易价差 z 分数,并验证关系

如何寻找、交易并验证配对?本页的 Python 配对交易回测对 105 组加密货币配对进行协整筛选,根据滚动回归价差的 z 分数交易其中最强的十组,再检验交易期间这些关系是否仍然成立。

VBT 提供构建统计套利工作流所需的组件:滚动回归、阈值信号、多空持仓、共享现金和参数比较。配对筛选规则、价差模型、仓位大小及离场方式由您决定。

Screen 105 pairs for cointegration on two years of data
from itertools import combinations
import statsmodels.tsa.stattools as ts

symbols = [
    "BTCUSDT", "ETHUSDT", "BNBUSDT", "XRPUSDT", "ADAUSDT", "SOLUSDT", "DOGEUSDT",
    "LTCUSDT", "LINKUSDT", "DOTUSDT", "TRXUSDT", "BCHUSDT", "XLMUSDT", "ETCUSDT",
    "ATOMUSDT",
]
data = vbt.BinanceData.pull(symbols, start="2021-01-01", end="2025-01-01")
log_close = np.log(data.close)
select = log_close.loc["2021":"2022"]  
pairs = list(combinations(symbols, 2))  
pvalues = pd.Series(
    {(s1, s2): ts.coint(select[s1], select[s2])[1] for s1, s2 in pairs},
    name="pvalue",
).rename_axis(["s1", "s2"])
top = pvalues.nsmallest(10)
top.round(4)
s1        s2
LINKUSDT  XLMUSDT     0.0003
BCHUSDT   XLMUSDT     0.0005
BNBUSDT   BCHUSDT     0.0005
          XLMUSDT     0.0006
          DOTUSDT     0.0007
          LTCUSDT     0.0007
          ATOMUSDT    0.0007
ADAUSDT   BCHUSDT     0.0009
BNBUSDT   LINKUSDT    0.0012
          ETCUSDT     0.0012
Name: pvalue, dtype: float64

十组配对均通过 Engle-Granger 检验,p 值接近 0.001。随后,每组配对作为共享同一现金余额的两列进行交易:

Trade every pair on z-score thresholds with shared cash
y = pd.concat({p: data.close[p[0]] for p in top.index}, axis=1, names=["s1", "s2"])
x = pd.concat({p: data.close[p[1]] for p in top.index}, axis=1, names=["s1", "s2"])
zscore = vbt.OLS.run(np.log(x), np.log(y), window=60, hide_params=True).zscore  

def legs(y_signal, x_signal):  
    both = pd.concat({"y": y_signal, "x": x_signal}, axis=1, names=["leg"])
    return both.reorder_levels(["s1", "s2", "leg"], axis=1).sort_index(axis=1)

def trade_pairs(period, threshold):
    z = zscore.loc[period]
    wide = z.vbt.crossed_above(threshold)  
    narrow = z.vbt.crossed_below(-threshold)
    revert = z.vbt.crossed_above(0) | z.vbt.crossed_below(0)
    return vbt.PF.from_signals(
        legs(y.loc[period], x.loc[period]),
        long_entries=legs(narrow, wide),
        short_entries=legs(wide, narrow),
        long_exits=legs(revert, revert),
        short_exits=legs(revert, revert),
        size=0.5,
        size_type="valuepercent",  
        fees=0.001,
        group_by=["s1", "s2"],
        cash_sharing=True,
        call_seq="auto",
    )

pf = trade_pairs("2023", 2.0)
metrics = ["total_return", "sharpe_ratio", "max_dd", "total_trades"]
pf.stats(metrics, agg_func=None).round(2)
                   Total Return [%]  Sharpe Ratio  Max Drawdown [%]  Total Trades
s1       s2
ADAUSDT  BCHUSDT             -90.43         -1.73             94.77            12
BCHUSDT  XLMUSDT             -35.72          0.28             74.99            10
BNBUSDT  ATOMUSDT             -2.12         -0.05             13.81            12
         BCHUSDT             -55.64         -1.56             61.99            12
         DOTUSDT             -11.63         -0.57             23.42            16
         ETCUSDT               9.38          0.58             10.82            16
         LINKUSDT              7.50          0.40             20.72            12
         LTCUSDT             -16.03         -0.53             30.92            12
         XLMUSDT               3.72          0.30             15.77            16
LINKUSDT XLMUSDT             -18.92         -0.76             33.01            18

并列结果清楚地区分了三组盈利配对和其余配对,其中两组损失超过一半本金。接下来检查选中的关系发生了怎样的变化:

Test the same pairs for cointegration while they were traded
later = log_close.loc["2023":"2024"]
still = pd.Series(
    {(s1, s2): ts.coint(later[s1], later[s2])[1] for s1, s2 in top.index}
)
pd.DataFrame({"2021-2022": top, "2023-2024": still}).round(3)
                   2021-2022  2023-2024
LINKUSDT XLMUSDT       0.000      0.712
BCHUSDT  XLMUSDT       0.001      0.594
BNBUSDT  BCHUSDT       0.001      0.864
         XLMUSDT       0.001      0.925
         DOTUSDT       0.001      0.964
         LTCUSDT       0.001      0.963
         ATOMUSDT      0.001      0.826
ADAUSDT  BCHUSDT       0.001      0.175
BNBUSDT  LINKUSDT      0.001      0.902
         ETCUSDT       0.001      0.825

后续检验发现,十组配对均不再满足 5% 显著性水平。VBT 将筛选、滚动模型和交易结果纳入同一工作流,便于研究关系变化,并比较新的筛选规则或离场条件。

寻找配对

协整比相关性更关键:两个相关资产可能永久分离,而协整配对的价差会回归均值。statsmodels 的 Engle-Granger 检验将一个价格对另一个价格回归,再检验残差的平稳性。对大规模资产池的所有配对逐一检验,会增加假阳性的概率。在 5% 显著性水平下,一些不存在协整关系的配对也可能偶然通过。因此应像上例一样,在一个时期筛选配对,在后续时期交易。

该检验并不对称。它将第一个价格对第二个价格回归,交换两者会得到略有不同的 p 值和对冲比率。每个无序配对只检验一次,可将工作量减半,代价是忽略另一个方向。

对于更大的资产池,@vbt.parameterized 可以使用并行线程检验所有配对,而带有 "s1 < s2" 等条件的 vbt.Param 能确保每个无序配对只构建一次。协整检验本身来自 statsmodels,在普通 Python 中运行,因此跨大量配对和窗口执行滚动协整检验会比较慢。只有滚动回归经过编译。参见参数优化。

价差信号

除交易外,您还可以检查滚动模型。vbt.OLS 提供斜率、截距、预测值、残差、z 分数和 R 平方。回归窗口和 z 分数标准化窗口可以分别设置,以比较模型和入场阈值适应变化的速度。参见滚动 OLS。

策略也可以使用您通过 pandas 或其他模型自行计算的价差。将价差转化为布尔型入场和离场数组,再传给同一个模拟器。例如,可以使用交易信号页中的穿越辅助方法,比较价差回到零时离场与进入更窄区间时离场的差异。

vbt.OLS 的滚动回归提供对冲比率、价差及其 z 分数,zscore_crossed_above 和 zscore_crossed_below 则将阈值转化为信号。同一配对的两条腿放入共享现金的同一组,入场时使用同一笔资金卖出一个资产并买入另一个资产,call_seq="auto" 确保卖出先执行。每组配对占投资组合的两列,因此十组或一千组配对都可以在一次模拟中运行,同一交易品种也可以出现在多个配对中。

上例两条腿采用金额中性配置:每条腿占配对净值的一半,对冲比率仅用于构建价差及其 z 分数。若要按对冲比率确定仓位,可根据回归输出的 slope 为每条腿构造 size 数组。2023 年,这些配对的 60 天斜率范围为 -1.5 至 4.1,因此按对冲比率配置也会使各次交易中两条腿的仓位发生很大变化。

检查每组配对和每条腿

示例为每组配对设置独立资金池,由其两条腿共享。即使同一资产出现在多个配对中,也能独立比较各组配对。配对标签在结果中区分这些持仓。若策略竞争同一账户的资金,应围绕该账户设置共享分组,具体参见投资组合记账。

配对的合并收益可能掩盖多头腿与空头腿截然不同的表现。检查订单与成交、交易记录及 pf.get_allocations(),了解持仓如何开立、平仓和确定规模。手续费和滑点可以按资产设置,以测试其中一条腿交易成本更高时的结果。

验证配对筛选和交易规则

阈值、回归窗口和离场规则都是参数。下一步在样本外进行测试:

Choose the threshold on 2023, then test it on 2024
thresholds = [1.5, 2.0, 2.5]
in_sample = {t: trade_pairs("2023", t).sharpe_ratio.mean() for t in thresholds}
best = max(in_sample, key=in_sample.get)
print(best, round(in_sample[best], 2))
2.5 -0.34
out_pf = trade_pairs("2024", best)
print(round(out_pf.sharpe_ratio.mean(), 2), (out_pf.sharpe_ratio > 0).mean())
0.05 0.3

2023 年表现最不差的阈值在 2024 年也表现最好,但十组配对中只有三组盈利,平均夏普比率接近零。若要使用更多窗口并滚动筛选配对,可使用前向滚动与交叉验证页中的切分器。

教程

会员专享的配对交易(英文)教程会筛选更大的资产池,并分别使用指标、Numba 和自定义模拟器构建相同策略。

版权所有 © 2021–2026 Oleg Polakow。保留所有权利。

本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。