登录
首页精彩阅读R语言构建配对交易量化模型
R语言构建配对交易量化模型
2017-04-07
收藏

R语言构建配对交易量化模型

散户每天都在经历中国股市的上蹿下跳,赚到钱是运气,赔钱是常态。那么是否有方法可以让赚钱变成常态呢?

我们可以通过“统计套利”的方法,发现市场的无效性。配对交易,就统计套利策略的一种,通过对冲掉绝大部分的市场风险,抓住套利机会,积累小盈利汇聚大收益。

1. 什么是配对交易?
配对交易(Pairs Trading)的理念最早来源于上世纪20年代华尔街传奇交易员Jesse Livermore 的姐妹股票对交易策略。配对交易的基本原理是找到两个相关性较高具备均衡关系的股票或其他金融产品,做空近期相对强势的金融产品,同时做多相对弱势金融产品,等待两者价格重返均衡值时进行平仓,赚取两者的价差变动的收益。

假设两个金融产品在未来的时期会保持良好的均衡关系,一旦两者之间的价格走势出现背离,同时这种背离在未来会被进行修复,那么就可能产生套利的机会。对于配对交易来说,就是找到这样的机会,进行统计套利。
配对交易的特点

配对交易与传统股票交易最大的不同之处在于,它的投资标的是两只股票的价差,是一种相对价值而非绝对价值。由于它在股票多头和空头方同时建仓,对冲掉了绝大部分的市场风险,所以它是一种市场的中性策略。无论大盘上涨还是下跌,配对交易策略收益都是相对平稳的,与大盘走势的相关性很低。

在市场无趋势性机会时,可以通过配对交易避免股市系统风险,获取Alpha绝对收益。趋势性的交易策略,可以参考文章 两条均线打天下。
配对交易操作方法
组合筛选:在市场上寻找用于配对的金融产品或者组合,检查历史价格的走势,判断是否可以用来进行配对。主要用下面几个指标来筛选配对组合:相关系数、模型计算的均值回复速度、协整检验、基本面因素等。通过这些因素来寻找出具有稳定相关关系的组合。
风险衡量和动态组合的构建:计算配对组合各自的预期收益、预期风险、交易成本;判断两个组合之间的价差服从何种分布;判断是具有长期均衡特性还是短期均衡特性;价差发生跳跃的频率等。
确定交易规则:根据价差的特性,确定交易的频率(高频交易还是低频交易),交易的触发条件和平仓规则等。
执行交易及风险控制:除了按照交易规则执行外,还必须动态跟踪价差走势,如果发现突变,应该及时调整套利模式和交易频率。
配对交易缺点
统计套利的规则都是基于历史数据计算的,但历史不能代表未来,当市场发生变化模型也会失效
市场对价格进行修复的时间难以准确判断,只能根据历史大致估计。如果回归的时间过长,对套利者的资金使用成本是个考验,也有可能导致套利失败。
2. 构建配对交易的模型
根据配对交易的原理,我们就可以自己设计配对交易的模型了。首先,需要把配对交易涉及的指标都进行量化,比如如何选择不同的两个具备均衡关系金融产品,什么时候做多,什么时候做空,什么时候平仓等。
根据概念,我们生成两个虚拟的金融产品X,Y,包括时间和价格字段。让X和Y的两个产品都价格符合正态分布,生成100个日期的数据。由于是测试程序,日期字段是包括了自然日,暂时理解为连续的日期。
R语言实现的代码如下:

> set.seed(1)                         #设置随机种子
> dates<-as.Date('2010-01-01')+1:100  #100个日期
> x<-round(rnorm(100,50,40),2)        #随机生成X产品,100个正态分析的收盘价
> y<-round(rnorm(100,50,40),2)        #随机生成Y产品,100个正态分析的收盘价
> df<-data.frame(dates,x,y)
> df
         dates      x      y
1   2010-01-02  24.94  25.19
2   2010-01-03  57.35  51.68
3   2010-01-04  16.57  13.56
4   2010-01-05 113.81  56.32
#  省略 ····
把数据进行可视化,可以更直观地理解数据本身。
# 加载R语言类库
> library(ggplot2)
> library(scales)
> library(reshape2)

# 数据转型
> df2<-melt(df,c('dates'))

# 画图
> g<-ggplot(data=df2,aes(x=dates,y=value,colour=variable))
> g<-g+geom_line()
> g<-g+scale_x_date(date_breaks = "1 week",date_labels='%m-%d')
> g<-g+labs(x='date',y='Price')
> g

上图中,X轴为时间,Y轴是价格,红色线为X的产品的价格,蓝色线为Y产品的价格。我们可以直观的看出,X,Y两个产品无任何关系。
根据配对交易的假设条件,如果两个金融产品的价差是收敛的。我们用X的产品价格减去Y产品的价格,当差值为正的时候,我们认为X的价格过高,则做空X,同时Y的价格过低,则做多Y;当差值为负的时候,我们认为X的价格过低,则做多X,同时Y的价格过高,则做空Y;当差值为0时,则价格被市场所修复,则全部平仓。
为了让差异更明显,我们定义的计算公式如下。
价差Z = X价格-Y价格
Z >  10时,做空X,做多Y ;Z<0时,平仓
Z < -10时,做多X,做空Y ;Z>0时,平仓
计算差价,然后计算交易统计。
# 计算差价
> df$diff<-df$x-df$y

# 找到差价大于10时的点
> idx<-which(df$diff>10)
> idx<-idx[-which(diff(idx)==1)-1]

# 打印差价的索引
> idx
 [1]  4 11 15 23 25 30 34 36 38 43 48 53 55 59 61 68 76 81 83 86 88 92 95 98
接下来,我们进行模拟交易,取第一个索引值的点,在2010-01-04时做空X,做多Y。当差价小于0在2010-01-06时,进行平仓。
# 打印前20个数据
> head(df,20)
        dates      x      y    diff
1  2010-01-02  24.94  25.19   -0.25
2  2010-01-03  57.35  51.68    5.67
3  2010-01-04  16.57  13.56    3.01
4  2010-01-05 113.81  56.32   57.49
5  2010-01-06  63.18  23.82   39.36
6  2010-01-07  17.18 120.69 -103.51
7  2010-01-08  69.50  78.67   -9.17
8  2010-01-09  79.53  86.41   -6.88
9  2010-01-10  73.03  65.37    7.66
10 2010-01-11  37.78 117.29  -79.51
11 2010-01-12 110.47  24.57   85.90
12 2010-01-13  65.59  31.53   34.06
13 2010-01-14  25.15 107.29  -82.14
14 2010-01-15 -38.59  23.97  -62.56
15 2010-01-16  95.00  41.70   53.30
16 2010-01-17  48.20  34.29   13.91
17 2010-01-18  49.35  37.20   12.15
18 2010-01-19  87.75  38.84   48.91
19 2010-01-20  82.85  69.77   13.08
20 2010-01-21  73.76  42.91   30.85

# 当差价大于10时,做空X,当差价小于0时,平仓。
# 第4行做空,第6行平仓
> xprofit<- df$x[4]-df$x[6];xprofit
[1] 96.63

# 当差价大于10时,做多Y;当差价小于0时,平仓。
# 第4行做空,第6行平仓
> yprofit<- df$y[6]-df$y[4];yprofit
[1] 64.37
从交易结果来看,我们第一笔配对交易就是赚钱的。

这是为什么呢?
根据配对交易的假设条件,如果两个金融产品的价差是收敛的,通过协整性检验的方法,我们可验证数据的收敛性。那么如果数据是收敛的,他还会具备均值回归的特性,请参考文章 均值回归,逆市中的投资机会。
画出X,Y的价差图,我们可以明显的看出,价差一直围绕着0上下波动,这是明显收敛的,同时符合均值回归的特性。

> plot(df$diff,type='l')

这就是市场的规则,通过配对交易的方法,我们找到市场无效性,从而可以赚去套利的收益。

3. 用R语言实现配对交易
看到上面的赚钱方法,也许大家会很兴奋!但是大部分市场的数据,都不会像我们的假设条件一样,轻而易举就能实现赚钱的目标。我们可以用计算机程序进行全市场的扫描发现交易机会,当然你也可以通过肉眼的方式来观察。
市场上有一些天生就具备均衡关系的金融产品,可以作为我们套利的入手对象。
股票类,同行业、市值和基本面相似的个股,比如,中国银行(601988)和农业银行(601288)。
基金类,以相同指数作为标的的不同基金,比如,证券B(150172),券商B(150201)。
期货类,同一期货品种的不同合约,比如,铜(cu1605, cu1606)。
混合类,跨市场为标的的金融产品,比如,沪深300指数,IF的期货合约
接下来,以相同品种不同合约的期货为例,我们把配对交易用在cu1605和cu1606的两个合约上,试试效果如何。由于期货是支持的T+0日内的交易的,而对于套利的操作,通常都不会持仓过夜,所以我们在尽量的短周期上进行操作,而且日内平仓。下面我将以1分钟做为交易周期。

3.1 数据准备
R语言本身提供了丰富的金融函数工具包,时间序列包zoo和xts,指标计算包TTR,可视包ggplot2等,我们会一起使用这些工具包来完成建模、计算和可视化的工作。关于zoo包和xts包的详细使用可以参考文章,R语言时间序列基础库zoo,可扩展的时间序列xts。

本文用到的数据,是铜的1分钟线的数据,从2016年日2月1日到2016年日2月29日,日盘的交易数据,以CSV格式保存到本地文件cu1605.csv,cu1606.csv。商品期货的日盘交易时间分为3段:09:00:00-10:14:59,10:30:00-11:29:59,13:30:00-14:59:59。当前测试,不考虑夜盘的数据。
数据格式如下:
2016-02-01 09:00:00,35870,35900,35860,35880
2016-02-01 09:01:00,35890,35890,35860,35870
2016-02-01 09:02:00,35870,35870,35860,35870
2016-02-01 09:03:00,35870,35900,35870,35900
2016-02-01 09:04:00,35900,35900,35870,35870
2016-02-01 09:05:00,35870,35880,35860,35870
2016-02-01 09:06:00,35880,35880,35860,35870
一共5列:
第1列,交易时间,date,2016-02-01 09:00:00
第2列,开盘价,Open,35870
第3列,最高价,High,35900
第4列,最低价,Low,35860
第5列,收盘价,Close,35880
通过R语言加载铜的1分钟线数据,因为我们进行日内交易,所以在加载时我就进行了转换,按日期进行分组,生成R语言的list对象,同时把每日的data.frame类型对象转成XTS时间序列类型对象,方便后续的数据处理
#加载工具包
> library(xts)
> library(TTR)

# 读取CSV数据文件
> read<-function(file){
+     df<-read.table(file=file,header=FALSE,sep = ",", na.strings = "NULL")  # 读文件
+     names(df)<-c("date","Open","High","Low","Close")                       # 设置列名
+     dl<-split(df,format(as.POSIXct(df$date),'%Y-%m-%d'))                   # 按日期分组
+     
+     lapply(dl,function(item){                                              # 换成xts类型数据
+         xts(item[-1],order.by = as.POSIXct(item$date))
+     })
+ }

# 加载数据
> cu1605<-read(file='cu1605.csv')
> cu1606<-read(file='cu1606.csv')

# 查看数据类型
> class(cu1605)
[1] "list"

# 查看数据的日期索引
> names(cu1605)
 [1] "2016-02-01" "2016-02-02" "2016-02-03" "2016-02-04" "2016-02-05"
 [6] "2016-02-15" "2016-02-16" "2016-02-17" "2016-02-18" "2016-02-19"
[11] "2016-02-22" "2016-02-23" "2016-02-24" "2016-02-25" "2016-02-26"
[16] "2016-02-29"

# 查看每日的数据量
> nrow(cu1605[[1]])
[1] 223

# 查看cu1605合约的数据
> head(cu1605[['2016-02-01']])
                     Open  High   Low Close
2016-02-01 09:00:00 35870 35900 35860 35880
2016-02-01 09:01:00 35890 35890 35860 35870
2016-02-01 09:02:00 35870 35870 35860 35870
2016-02-01 09:03:00 35870 35900 35870 35900
2016-02-01 09:04:00 35900 35900 35870 35870
2016-02-01 09:05:00 35870 35880 35860 35870
把数据准备好了,我们就可以来建立模型了。

3.2 配对交易模型
以2016年02月01日为例进行交易,以1分钟线的close价格来计算cu1605和cu1606的两个合约的价差。下面我们对数据进行操作,合并2个合约在2016年02月01日的数据,并对空值进行处理,最后计算出两个合约的价差。
# 合并数据
> xdf<-merge(cu1605[['2016-02-01']]$Close,cu1606[['2016-02-01']]$Close)
> names(xdf)<-c('x1','x2')

# 用前值替换空值
> xdf<-na.locf(xdf)

# 计算价差
> xdf$diff<-xdf$x1-xdf$x2

# 打印前20行数据
> head(xdf,20)
                     x1     x2     diff
2016-02-01 09:00:00  35880  35900  -20
2016-02-01 09:01:00  35870  35920  -50
2016-02-01 09:02:00  35870  35910  -40
2016-02-01 09:03:00  35900  35940  -40
2016-02-01 09:04:00  35870  35910  -40
2016-02-01 09:05:00  35870  35920  -50
2016-02-01 09:06:00  35870  35910  -40
2016-02-01 09:07:00  35860  35910  -50
数据解释:
x1列,为第一腿对应cu1605合约
x2列,为第二腿对应cu1606合约。
diff列,为cu1605-cu1606
从价差的结果看,每1分钟cu1605合约都小于cu1606合约,从-110到-20价差不等,并且以-63为均值上下反复震荡。
# 计算价差范围
> range(xdf$diff)
[1] -110  -20

# 计算价差均值
> mean(xdf$diff)
[1] -63.90135

# 画出价差分布柱状图
> hist(xdf$diff,10)
画出价差分布柱状图

我们假设以-63为均值回归点,当差值为大于-45的时候,认为X的价格过高做空X,同时Y的价格过低做多Y;当差值小于-75的时候,我们认为X的价格过低做多X,同时Y的价格过高做空Y;当差值为-63时,价格被市场所修复,则全部平仓。以cu1605和cu1606的两个合约按照1:1持仓进行配比,1手多单对1手空单。
定义模型指标,计算价值列为diff,均值回归列为mid,最大阈值列为top,最小阈值列为bottom。
target.pair<-function(xdf){
  xdf$diff<-xdf$x1-xdf$x2   #差值
  xdf$mid<- -63             #均值回归点
  xdf$top<- -45             #最大阈值
  xdf$bottom<- -75          #最小阈值
  return(xdf)
}
完成指标的定义后,我们创建配对交易模型,并对合同数据进行回测,产生交易信号后,模拟交易输出清单,并可视化交易结果。
回测过程代码省略,产生的交易信号如下所示。
                  date    x1    x2 diff mid top bottom op
21 2016-02-01 09:00:00 35880 35900  -20 -63 -45    -75 ks
1  2016-02-01 09:25:00 35740 35810  -70 -63 -45    -75 pb
22 2016-02-01 09:40:00 35690 35730  -40 -63 -45    -75 ks
2  2016-02-01 09:47:00 35700 35770  -70 -63 -45    -75 pb
13 2016-02-01 10:00:00 35690 35770  -80 -63 -45    -75 kb
5  2016-02-01 10:01:00 35710 35760  -50 -63 -45    -75 ps
23 2016-02-01 10:02:00 35710 35750  -40 -63 -45    -75 ks
3  2016-02-01 10:07:00 35680 35750  -70 -63 -45    -75 pb
数据解释:
date列,为交易时间
x1列,为第一腿对应cu1605合约
x2列,为第二腿对应cu1606合约。
diff列,为cu1605-cu1606
mid列,为均值回归点
top列,为最大阈值
bottom列,为最小阈值
op列,为交易信号
交易信号一共有4种。
ks, 开仓, 做空(卖),对应反向操作为pb。
kb, 开仓, 做多(买),对应反向操作为ps。
ps, 平仓, 做空(卖),对应反向操作为kb。
pb,平仓, 做多(买),对应反向操作为ks。
一共出现了24个交易信号,由于我们进行的是配对交易,所以当出现ks(开仓做空)信号时,实际上会进行2笔操作,开仓做空第一腿,开仓做多第二腿。
接下来,进行模拟交易,计算出交易清单。
$x1
                       code op price pos    fee  value  margin balance     cash
2016-02-01 09:00:00  cu1605 ks 35880   1 8.9700 179400 26910.0      NA 173081.0
2016-02-01 09:25:00  cu1605 pb 35740   0 8.9350      0     0.0     700 173748.1
2016-02-01 09:40:00  cu1605 ks 35690   1 8.9225 178450 26767.5      NA 173437.7
2016-02-01 09:47:00  cu1605 pb 35700   0 8.9250      0     0.0     -50 173339.9
2016-02-01 10:00:00  cu1605 kb 35690   1 8.9225 178450 26767.5      NA 173552.0
2016-02-01 10:01:00  cu1605 ps 35710   0 8.9275      0     0.0     100 173574.2
数据解释:
$x1部分,为第一腿的交易清单。
$x2部分,为第二腿的交易清单。
code,合约代码
op,交易信号
price,成交价格
pos,成交数量
fee,手续费
value,对应价值
margin,保证金
balance,平仓盈亏
cash,账号资金
我通过交易清单,统计交易结果。
> page  
$day     # 交易日期
[1] "2016-02-01"

$capital   # 初始资金
[1] 2e+05

$cash      # 账户余额
[1] 201221.4

$num       # 交易信号数
[1] 24

$record    # 配对交易平仓盈亏
                      x1   x2 balance
2016-02-01 09:25:00  700 -450     250
2016-02-01 09:47:00  -50  200     150
2016-02-01 10:01:00  100   50     150
2016-02-01 10:07:00  150    0     150
2016-02-01 10:42:00  100   50     150
2016-02-01 11:21:00   50  150     200
2016-02-01 11:23:00  100   50     150
2016-02-01 13:36:00 -150  250     100
2016-02-01 13:46:00   50   50     100
2016-02-01 13:53:00    0  100     100
2016-02-01 14:49:00 -200  300     100
2016-02-01 14:58:00    0   50      50

$balance   # 汇总平仓盈亏,第一腿盈亏,第二腿盈亏
[1] 1650  850  800

$fee       # 汇总手费费,第一腿手续费,第二腿手续费
[1] 429 214 215

$profit    # 账户净收益,收益率(占保证金)
[1] 1221.000    0.023

$wins      # 胜率,胜数,败数
[1]  1 12  0
最后,通过可视化输出交易信号。

图例解释
棕色线,为价差diff
紫色线,为最大阈值top
红色线,为最小阈值bottom
蓝色线,为均值线mid,平行于top和bottom
浅蓝线,为ks开仓做空的交易
绿色线,为kb开仓做多的交易
从图中看就更直观了,我们进行了12次交易,每次4笔,胜率100%。
最后,我们对2月份整个的数据进行回测。回测结果如下。

         date profit    ret balance fee winRate win fail maxProfit maxLoss avgProfit avgLoss
1  2016-02-01   1221  0.023    1650 429    1.00  12    0       250      50       138     NaN
2  2016-02-02   1077  0.020    1650 573    1.00  15    0       150       0       110     NaN
3  2016-02-03     64  0.001     100  36    1.00   1    0       100     100       100     NaN
4  2016-02-04    113  0.002     150  37    1.00   1    0       150     150       150     NaN
5  2016-02-05    926  0.017    1400 474    1.00  13    0       150     100       108     NaN
数据解释:
date,交易日期
profit,净收益
ret,每日收益率
balance,平仓盈亏
fee,手续费
winRate,胜率
win,胜数
fail,败数
maxProfit,单笔最大盈利
maxLoss,单笔最大亏损
avgProfit,平均盈利
avgLoss,平均亏损
从结果来看,多么开心啊,几乎每天都是赚钱的!!
cu1605和cu1606两个合同是完美地具备均衡关系的两个金融产品,大家常常所说的跨期套利就是基于这个思路实现的。本文介绍的配对交易模型,是统计套利的一个基本模型,原理很简单,当大家都掌握后拼的就是交易速度了。数据分析师培训
利用市场的无效性来获取利润,是每个套利策略都在寻找的目标。通过统计方法,我们可以发现市场的无效性,再以对冲的操作方式,规避绝大部分的市场风险,等待市场的自我修复后来赚钱利润。说起来很简单,但市场的无效性,可能会在极短时间内就被修复。
“天下武功为快不破”,通过量化的手段,让计算机来发现机会,进行交易,实现收益。一切就和谐了!!

数据分析咨询请扫描二维码

客服在线
立即咨询