okaa

配对交易最完整指南+在加密货币市场实现一个pairs trading案例!

本篇文章的第一部分我们再认识一遍配对交易的原理,第二部分我们来看看配对交易在不同市场(期货、股票、数字货币、外汇)的表现,第三部分我们来看一个加密货币上配对交易的实现案例

配对交易个性档案

配对交易诞生于1980年的摩根斯坦利,交易员Nunzio Tartaglia和他的团队第一次实现了配对交易(这帮人后来创立了two sigma)。

但是在此之前,就有大佬使用过配对交易了,他就是利弗莫尔,用肉眼观察同类资产表现然后做单。
配对交易(pairs trading)是一种主流的高频交易策略,属于统计套利的一种,具体做法是①先找到两种走势高度相关的资产,他俩的价差总是维持在一个恒定的数值 ②发现两种资产出现了定价错误,两者价差扩大 ③做空一种资产,做多另一种资产,期望他们的价差会回归到常态。

在之前的公众号文章里,我们有讲过配对交易,带你走一遍量化交易全过程:配对策略的python实现选择了澳大利亚ETF(EWA)和加拿大ETF(EWC)两组资产,它俩的价格走势看上去亦步亦趋。

下面这个例子也演示了一种配对交易的玩法:图1是两组资产的价格走势,图2是两组资产的价差变化。两种资产的价格走势高度相关,且价差维持在一个相对稳定的差值。但是方框中两种资产的价格出现了扭转,打破了以往的规律,这时候我们就可以通过做多资产一,做空60%的资产二来实现配对策略

配对交易的背后原理就是投机的本质,买入低估资产,卖出高估资产:

buy undervalued and sell overvalued

要知道一个资产是被高估还是低估,我们必须要知道它的真实定价(fair value)是多少,但是得知真实定价是一个很棘手的问题。而配对交易利用价格相关性,轻松解决了这个问题。

如果两种资产有相同的特征和风险暴露,那么我们可以认为它倆的价格走势也相似,我们并不需要知道资产的真实定价,只用关注两者价格走势之间的关系(或者收益率之间的相关性)—两者价格的差值或者比值应该维持在某个恒定的值,如果价差扩大,可能是其中一种资产价格被高估,另一种资产价格被低估。因此配对交易也是一种市场中性策略,赚不赚钱和市场涨跌无关。

严格的配对交易流程有3个步骤。

第1步:通过协整性检验和距离度量等方法确定两组高度相关的资产(简而言之,如果两组资产价格的比值倾向于收敛到一个数值,这两者就有高度协整性)

要注意协整性和相关性是不同的,比如下面X和Y两组数据,左图是X和Y的价格走势,右图是X和Y的价差,这两组数据虽然是高度相关的,但是没有协整性

第2步:确保市场中性和最大化均值回归的原则下,对两者的价差进行建模。

第3步:制定具体的交易策略。在具体策略的制定环节,要着眼于市场的微观结构,对价格波动、价差、订单薄、交易量流动性等等进行细节上的考量。

不同市场的配对交易

因为可选择的品种多,所以权益市场一直是配对交易者最喜欢的战场,当然,大宗商品、外汇甚至加密货币等市场也是很好的选择,这些市场都有大量关于统计套利盈利能力的支持性研究。

权益市场&ETF市场
Jacobs和Weber在34个新兴股票市场中使用了配对策略,证实了配对策略是最具盈利能力的策略。
但是在股票市场做配对交易,有一个非常大的限制,那就是卖空很不自由。卖空需要抵押物,还需要找到愿意把股票借给你拿去卖的人。有些市场有严格的做空限制,比如规定只有在股价上涨时才允许卖空股票。

期货市场
卖空期货没有卖空股票那么多限制。配对交易毕竟是高频交易的一种,对速度和仓位大小要精确计算,所以要很准确的原始数据来对模型回测。有一些配对交易还需要考虑到合约规模和隐含波动率来确定合适的对冲比率。
luckboxmagazine.com/tec
上面连接里的这篇文章,举了黄金和白银期货配对交易的例子,文中详细讲了如何根据隐含波动率和合约规模来设置对冲比率(合约规模:一个白银合约对应5000盎司的白银,而一个黄金合约只对应100盎司的黄金)。

外汇市场
外汇市场最大的问题是流动性问题,这个问题可以通过用流动性好的货币充当媒介来解决。比如说当做澳元/加元的配对交易时,因为澳元/加元的流动性并不好,所以同时开澳元/美元,美元/加元的单子,这种配对交易被叫做“合成配对交易(synthetic pairs)”。

加密货币市场
在小时间级别的市场无效性很明显,存在大量的套利机会。现在的加密货币市场可选择的品种越来越多。

加密货币的配对交易实现

我们从Bitfinex获取7个币种从2018–01–01到2018–05–31的数据:

然后对这些币种的协整性做假设检验,得到p-value:

import statsmodels.tsa.stattools as ts 
test_result = ts.coint(crypto_prices[a1], crypto_prices[a2])
print(a1 + ' and ' + a2 + ': p-value = ' + str(test_result[1]))

当p值小于阈值,则拒绝原假设,即两个品种间不存在协整性。现在我们找到了2个品种A和B,接下来构建策略。

BTCUSD and ETHUSD: p-value = 0.06576979804268955
BTCUSD and LTCUSD: p-value = 0.07347140678450967
BTCUSD and XMRUSD: p-value = 0.021570889424181703
BTCUSD and NEOUSD: p-value = 0.10239483419041967
BTCUSD and XRPUSD: p-value = 0.00900122457399106
BTCUSD and ZECUSD: p-value = 0.16378128244807538
ETHUSD and BTCUSD: p-value = 0.31796015423321283
ETHUSD and LTCUSD: p-value = 0.609075825185015

首先计算A和B一段时期的收益率之差:

计算z-score:

然后制定开仓规则,当z-score大于小于某个分位时,分别对A和B开多空头寸,这里直接用代码讲解:

def initialize(context):
    # A和B是用于配对交易的2个品种
    context.A = symbol('xmr_usd')
    context.B = symbol('neo_usd')
    context.leverage = 1.0                              # 1.0 - 没有杠杆
    context.n_modelling = 144                           # 用于建模的回溯k线数量
    context.tf = str(30) + "T"                          # 一个时间窗口里有多少分钟?1 - 1分钟;60 - 1小时
    # 确定进出场z-score阈值,这里用到了st.norm.ppf函数
    context.z_signal_in = st.norm.ppf(1 - 0.05 / 2)     # 开仓的z-score阈值
    context.z_signal_out = st.norm.ppf(1 - 0.60 / 2)    # 平仓的z—score阈值
    context.min_spread = 0.01                           # 允许的最小收益率之差
    context.set_commission(maker = 0.000, taker = 0.000) # 定义手续费,这里都没有手续费
    context.set_slippage(slippage = 0.0000) # 定义滑点,这里没有滑点

def handle_data(context, data): # 数据处理函数
    current_time = get_datetime().time()
    # 获取数据
    A = data.history(context.A,'price',bar_count = context.n_modelling,frequency = context.tf)
    B = data.history(context.B,'price',bar_count = context.n_modelling,frequency = context.tf)
    # 计算收益率,计算收益率的差spread,计算z-score
    A_return = A.pct_change()
    B_return = B.pct_change()
    spread = A_return - B_return
    # 计算z-score的标准差
    zscore = (spread.iloc[-1] - spread.mean()) / spread.std()
    # 平仓
    # 当持有B的空头头寸,z-score >= 平仓阈值时,平仓
    if context.portfolio.positions[context.B].amount < 0 and zscore >= -context.z_signal_out:
        order_target_percent(context.A,  0.0)
        order_target_percent(context.B,  0.0)
    # 当持有B的多头头寸,z-score <= 平仓阈值时,平仓
    if context.portfolio.positions[context.B].amount > 0 and zscore <= context.z_signal_out:
        order_target_percent(context.A,  0.0)
        order_target_percent(context.B,  0.0)

    # 开仓
    # 当前的spread > 允许的最小收益率之差,这一步的作用是让spread高于往返交易成本,不至于让交易成本吃掉利润
    if (abs(spread[-1]) >= context.min_spread):# and np.sign(A_return[-1] * B_return[-1]) < 0:
       # 当zscore > 开仓阈值时,用50%的资金做空A,用50%的资金做多B
       if context.portfolio.positions[context.B].amount == 0 and zscore > context.z_signal_in:
           order_target_percent(context.A,  -0.5 * context.leverage)
           order_target_percent(context.B,  0.5 * context.leverage)
       # 当zscore < -开仓阈值时,用50%的资金做多A,用50%的资金做空B
       if context.portfolio.positions[context.B].amount == 0 and zscore < -context.z_signal_in:
           order_target_percent(context.A,  0.5 * context.leverage)
           order_target_percent(context.B,  -0.5  * context.leverage)

    record(
        A_return = A_return[-1],
        B_return = B_return[-1],
        spread = spread[-1],
        zscore = zscore
    )

最后,策略的表现与交易次数和spread非常相关,我们可以把开平仓的z-score阈值设置的大一点,同时把spread_min也就是最小收益率之差设置的大一点,通过调整这两个参数来改进策略的表现。

加密货币和外汇市场有不少配对交易的机会,但是别忘了配对交易仍然属于高频的一种,要长期盈利还是得上规模。

退出移动版