掘金社区

以ETF为例——配对交易Python源码全公开Pinned highlighted

掘金小Q 0 发表在策略研究 2021-08-16 13:53:48

策略研究
188
0
0

配对交易是指八十年代中期华尔街著名投行Morgan Stanley的数量交易员Nunzio Tartaglia成立的一个数量分析团队提出的一种市场中性投资策略。

在实际操作中,其执行过程可以简单地描述为:投资者首先选择相互匹配的两个资产,当配对资产价格差异增加的时候,做多价格偏低的资产,同时做空价格偏高的资产,而当价格差异减小的时候,则结束头寸,完成交易;同时,为了控制风险,当价差进一步扩大时,需要在适当的止损点结束头寸。

所以,想开发一个配对交易的策略,核心有两个步骤:

1.找到待交易的标的。

比如说两个走势非常类似的股票或者其他证券。

2.对两者的价差建模。

假设是两者的价差会在一个稳定的区间波动(均值回归)。

————————————————

在以往的研究中,配对交易的标的的筛选方法有很多种,归纳起来大致有这两种:

  • 最小化偏差平方和法则(最小距离法)

  • 协整理论方法

今天,我们以ETF为例。

首先获取深交所的所有交易标的,剔除掉在今年才上市和已经退市的,筛选出深交所的所有ETF基金,然后通过相关性检验取出相关系数最强的一对cp进行配对交易。

具体执行如下:

1.先获取深交所的所有交易标的

import numpy as np
import pandas as pd
from gm.api import *
import matplotlib.pyplot as plt
set_token('xxxxxxxxxxxx')
#获取深圳交易所全部标的
data1=get_instruments(exchanges='SZSE',df=True)
#剔除掉在今年才上市的标的
data1=data1[data1['listed_date']<'2020-12-30']
#剔除掉今天之前退市的标的
data1=data1[data1['delisted_date']>'2021-08-30']
data1.index=range(len(data1))
data1


2.取出ETF基金。

b=pd.DataFrame()
symbol=[]

for i in range(len(data1)):
    if 'ETF' in data1['sec_name'][i]:
        print(data1['sec_name'][i])
        symbol.append(data1['symbol'][i])
        if len(b)==0:
            b=data1[data1['sec_name']==str(""+data1['sec_name'][i]+"")]
        else:
            b=b.append(data1[data1['sec_name']==str(""+data1['sec_name'][i]+"")])
b            

获取的部分ETF如下,一共114支。

0_1629092821430_eac80c8f-d251-4568-9f5e-01253eafc205-image.png

3.获取这114支ETF的最近半年的历史数据代码。

这里理论上应该获取回测时间点前半年的数据出来研究,但这里暂且用今年前半年多的数据来研究,读者可以自行修改数据的时间参数。

data2=pd.DataFrame()
for i in b['symbol']:
    history_data = history(symbol=i, frequency='1d', start_time='2021-01-28',end_time='2021-07-30', fields='close', adjust=ADJUST_PREV, df= True)
    data2["'"+i+"'"]=history_data['close']

最终获取的数据如下,一共122个交易日,114支ETF。

0_1629092928713_a40b5a61-f3bf-421f-be2a-60bb5c593e3a-image.png


4.由于数据量太大,这里取出前六支ETF,计算相关系数并画出热力图,如下所示:

0_1629092984631_e424a57c-cc48-45a8-9af8-398f75bdf885-image.png


5.根据相关系数进行排序,取出相关系数最强的十组进行“强行cp”,结果如下所示:

0_1629093066400_1fb24717-84d5-4d10-b4b7-c08df086ed4a-image.png


最终,根据数值选出的最佳配对为:SZSE.159801(广发国证半导体芯片ETF) 和SZSE.159995(华夏国证半导体芯片ETF),其收盘价数据变化对比如下:

0_1629093100832_6c226563-ed24-4d1b-b8af-5ea548cf0d57-image.png

经计算,两者在过去半年中的相关系数为99.99%,已是相当高了。我们接着再用配对交易的一般策略验证这对标的进行配对交易的成效。

————————————————

策略思想

获取两支ETF基金交易情况,如果停牌,则跳过。

计算前五分钟,两者收盘价差值,并计算差值的方差及平均值。

依据以上数据,构造差值的布林通道。

当价差上破布林上轨,做空价差(半仓)。

当价差下破布林下轨,做多价差(半仓)。


回测参数均设置为:

**时间:**2021-01-01至2021-08-10

**调仓频率:**1天

**基准指数:**沪深300指数(SHSE.000300)

**标的:SZSE.159801(广发国证半导体芯片ETF)、SZSE.159995(华夏国证半导体芯片ETF)

**滑点:**0.0001

**手续费:**0.0001

策略收益概况:

0_1629093158556_ca0a0fc1-2f2f-48a8-9a15-a8cf673d29aa-image.png

(本次回测由掘金量化提供)

注:配对交易还有很多策略思想,市场中还有很多标的可以构建cp,此处抛砖引玉,仅供参考。


附录:

# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *
import  numpy as np

#本策略基于掘金量化交易平台 网址:www.myquant.cn

def init(context):
    #获得N根基金Bar数据
    context.N=5
    #选择一对基金
    context.stock=['SZSE.159801','SZSE.159995']
    # 每个交易日的09:40 定时执行algo任务
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:40:00')

def algo(context):
    # 获取上一个交易日的日期
    last_day = get_previous_trading_date(exchange='SZSE', date=context.now)
    # 获取当天有交易的基金
    not_suspended = get_history_instruments(symbols=context.stock, start_date=last_day, end_date=last_day)
    # 计算当天有交易非停牌的基金个数
    a =  len([item['symbol']for item in not_suspended if not item['is_suspended']])
    #如果有一支停牌,就跳过
    if a<2:
        return
    #获得历史交易数据
    prices1 = history_n(symbol=context.stock[0], frequency='300s', count=context.N, end_time=last_day, fields='close',skip_suspended=True,adjust=ADJUST_PREV,df=True)
    prices2 = history_n(symbol=context.stock[1], frequency='300s', count=context.N, end_time=last_day, fields='close',skip_suspended=True,adjust=ADJUST_PREV,df=True)
    
    p1=list(prices1['close'])
    p2=list(prices2['close'])

    spread = np.array(p1[:-1]) - np.array(p2[:-1])
    # 计算布林带的上下轨
    up = np.mean(spread) + 2 * np.std(spread)
    down = np.mean(spread) - 2 * np.std(spread)
    # 计算最新价差
    spread_now = p1[-1] - p2[-1]
    # 无交易时若价差上(下)穿布林带上(下)轨则做空(多)价差
    position_s1_long = context.account().position(symbol=context.stock[0], side=PositionSide_Long)
    position_s2_long = context.account().position(symbol=context.stock[1], side=PositionSide_Long)
    if not position_s1_long and not position_s2_long:
        if spread_now > up:
            order_target_percent(symbol=context.stock[1], percent=0.5, order_type=OrderType_Market,
                                 position_side=PositionSide_Long)
            print(context.stock[1],'价差上穿布林带,做多价差')

        if spread_now < down:
            order_target_percent(symbol=context.stock[0], percent=0.5, order_type=OrderType_Market,
                                 position_side=PositionSide_Long)
            print(context.stock[0],'价差下穿布林带,做空价差')                     
            # 价差回归时平仓
    elif position_s2_long:
        if spread_now <= up:
            order_close_all()
            print('价差回归时平仓')
    elif position_s1_long:
        if spread_now >= down:
            order_close_all()
            print('价差回归时平仓')
  
if __name__ == '__main__':
    '''
    strategy_id策略ID,由系统生成
    filename文件名,请与本文件名保持一致
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
    token绑定计算机的ID,可在系统设置-密钥管理中生成
    backtest_start_time回测开始时间
    backtest_end_time回测结束时间
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
    backtest_initial_cash回测初始资金
    backtest_commission_ratio回测佣金比例
    backtest_slippage_ratio回测滑点比例
    '''
    run(strategy_id='xxxxxxxx',
        filename='qixi.py',
        mode=MODE_BACKTEST,
        token='xxxxxxxxxxxx',
        backtest_start_time='2021-01-01 08:00:00',
        backtest_end_time='2021-08-10 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=10000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001)

声明:本内容仅供学习、交流、演示之用,不构成任何投资建议!
暂无评论

Looks like your connection to 掘金量化社区 - 量化交易者的交流社区 was lost, please wait while we try to reconnect.