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两个合同是完美地具备均衡关系的两个金融产品,大家常常所说的跨期套利就是基于这个思路实现的。本文介绍的配对交易模型,是统计套利的一个基本模型,原理很简单,当大家都掌握后拼的就是交易速度了。数据分析师培训
利用市场的无效性来获取利润,是每个套利策略都在寻找的目标。通过统计方法,我们可以发现市场的无效性,再以对冲的操作方式,规避绝大部分的市场风险,等待市场的自我修复后来赚钱利润。说起来很简单,但市场的无效性,可能会在极短时间内就被修复。
“天下武功为快不破”,通过量化的手段,让计算机来发现机会,进行交易,实现收益。一切就和谐了!!
数据分析咨询请扫描二维码
数据分析需要学习的内容非常广泛,涵盖了从理论知识到实际技能的多个方面。以下是数据分析所需学习的主要内容: 数学和统计学 ...
2024-11-24数据分析师需要具备一系列多方面的技能和能力,以应对复杂的数据分析任务和业务需求。以下是数据分析师所需的主要能力: 统计 ...
2024-11-24数据分析师需要学习的课程内容非常广泛,涵盖了从基础理论到实际应用的多个方面。以下是根据我搜索到的资料整理出的数据分析师需 ...
2024-11-24《Python数据分析极简入门》 第2节 6 Pandas合并连接 在pandas中,有多种方法可以合并和拼接数据。常见的方法包括append()、conc ...
2024-11-24《Python数据分析极简入门》 第2节 5 Pandas数学计算 importpandasaspdd=np.array([[81,&n ...
2024-11-23数据分析涉及多个方面的学习,包括理论知识和实践技能。以下是数据分析需要学习的主要方面: 基础知识: 数据分析的基本概念 ...
2024-11-22数据分析适合在多个单位工作,包括但不限于以下领域: 金融行业:金融行业对数据分析人才的需求非常大,数据分析师可以从事经 ...
2024-11-22数据分析是一种涉及从大量数据中提取有用信息和洞察力的过程。其工作内容主要包括以下几个方面: 数据收集与整理:数据分析师 ...
2024-11-22数据分析师需要掌握多种技能,以确保能够有效地处理和分析数据,并为业务决策提供支持。以下是数据分析师需要掌握的主要技能: ...
2024-11-22数据开发和数据分析是两个密切相关但又有所区别的领域。以下是它们的主要区别: 定义和目标: 数据开发:数据开发涉及数据的 ...
2024-11-22数据架构师是负责设计和管理企业数据架构的关键角色,其职责涵盖了多个方面,包括数据治理、数据模型设计、数据仓库构建、数据安 ...
2024-11-22数据分析师需要具备一系列技能,以确保能够有效地处理、分析和解释数据,从而支持决策制定。以下是数据分析师所需的关键技能: ...
2024-11-22数据分析师需要具备一系列技能,以确保能够有效地处理、分析和解释数据,从而支持决策制定。以下是数据分析师所需的关键技能: ...
2024-11-22数据分析师需要具备一系列的技能和能力,以确保能够有效地处理、分析和解释数据,从而支持业务决策。以下是数据分析师所需的主要 ...
2024-11-22需求持续增长 - 未来数据分析师需求将持续上升,企业对数据驱动决策的依赖加深。 - 预测到2025年,中国将需要高达220万的数据人 ...
2024-11-22《Python数据分析极简入门》 第2节 4 Pandas条件查询 在pandas中,可以使用条件筛选来选择满足特定条件的数据 importpanda ...
2024-11-22数据分析师的工作内容涉及多个方面,主要包括数据的收集、整理、分析和可视化,以支持商业决策和问题解决。以下是数据分析师的一 ...
2024-11-21数据分析师必须掌握的技能可以从多个方面进行归纳和总结。以下是数据分析师需要具备的主要技能: 统计学基础:数据分析师需要 ...
2024-11-21数据分析入门的难易程度因人而异,总体来看,入门并不算特别困难,但需要一定的学习和实践积累。 入门难度:数据分析入门相对 ...
2024-11-21数据分析是一项通过收集、整理和解释数据来发现有用信息的过程,它在现代社会中具有广泛的应用和重要性。数据分析能够帮助人们更 ...
2024-11-21