• Posts tagged "量化选股"

Blog Archives

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

用IT技术玩金融系列文章,将介绍如何使用IT技术,处理金融大数据。在互联网混迹多年,已经熟练掌握一些IT技术。单纯地在互联网做开发,总觉得使劲的方式不对。要想靠技术养活自己,就要把技术变现。通过“跨界”可以寻找新的机会,创造技术的壁垒。

金融是离钱最近的市场,也是变现的好渠道!今天就开始踏上“用IT技术玩金融”之旅!

关于作者:

  • 张丹(Conan), 程序员Java,R,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/finance-pairs-trading/

pair-trading

前言

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

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

目录

  1. 什么是配对交易?
  2. 配对交易的模型
  3. 用R语言实现配对交易

1. 什么是配对交易?

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

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

配对交易的特点

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

在市场无趋势性机会时,可以通过配对交易避免股市系统风险,获取Alpha绝对收益。趋势性的交易策略,可以参考文章 两条均线打天下

配对交易操作方法

  1. 组合筛选:在市场上寻找用于配对的金融产品或者组合,检查历史价格的走势,判断是否可以用来进行配对。主要用下面几个指标来筛选配对组合:相关系数、模型计算的均值回复速度、协整检验、基本面因素等。通过这些因素来寻找出具有稳定相关关系的组合。
  2. 风险衡量和动态组合的构建:计算配对组合各自的预期收益、预期风险、交易成本;判断两个组合之间的价差服从何种分布;判断是具有长期均衡特性还是短期均衡特性;价差发生跳跃的频率等。
  3. 确定交易规则:根据价差的特性,确定交易的频率(高频交易还是低频交易),交易的触发条件和平仓规则等。
  4. 执行交易及风险控制:除了按照交易规则执行外,还必须动态跟踪价差走势,如果发现突变,应该及时调整套利模式和交易频率。

配对交易缺点

  • 统计套利的规则都是基于历史数据计算的,但历史不能代表未来,当市场发生变化模型也会失效
  • 市场对价格进行修复的时间难以准确判断,只能根据历史大致估计。如果回归的时间过长,对套利者的资金使用成本是个考验,也有可能导致套利失败。

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
5   2010-01-06  63.18  23.82
6   2010-01-07  17.18 120.69
7   2010-01-08  69.50  78.67
8   2010-01-09  79.53  86.41
9   2010-01-10  73.03  65.37
10  2010-01-11  37.78 117.29
11  2010-01-12 110.47  24.57
12  2010-01-13  65.59  31.53
13  2010-01-14  25.15 107.29
14  2010-01-15 -38.59  23.97
15  2010-01-16  95.00  41.70
16  2010-01-17  48.20  34.29
17  2010-01-18  49.35  37.20
18  2010-01-19  87.75  38.84
19  2010-01-20  82.85  69.77
20  2010-01-21  73.76  42.91
21  2010-01-22  86.76  29.76
22  2010-01-23  81.29 103.72
23  2010-01-24  52.98  41.42
24  2010-01-25 -29.57  42.82
25  2010-01-26  74.79  45.99
26  2010-01-27  47.75  78.51
27  2010-01-28  43.77  47.06
28  2010-01-29  -8.83  48.49
29  2010-01-30  30.87  22.73
30  2010-01-31  66.72  37.03
31  2010-02-01 104.35  52.41
32  2010-02-02  45.89  26.44
33  2010-02-03  65.51  71.26
34  2010-02-04  47.85 -10.74
35  2010-02-05  -5.08  62.26
36  2010-02-06  33.40 -11.46
37  2010-02-07  34.23  37.96
38  2010-02-08  47.63  28.87
39  2010-02-09  94.00  23.92
40  2010-02-10  80.53  47.72
41  2010-02-11  43.42 -26.57
42  2010-02-12  39.87  97.06
43  2010-02-13  77.88 -16.60
44  2010-02-14  72.27  31.46
45  2010-02-15  22.45   5.36
46  2010-02-16  21.70  19.97
47  2010-02-17  64.58 133.49
48  2010-02-18  80.74  50.70
49  2010-02-19  45.51  -1.45
50  2010-02-20  85.24 -15.62
51  2010-02-21  65.92  68.01
52  2010-02-22  25.52  49.26
53  2010-02-23  63.64  37.28
54  2010-02-24   4.83  12.83
55  2010-02-25 107.32  -9.50
56  2010-02-26 129.22   6.99
57  2010-02-27  35.31  90.00
58  2010-02-28   8.23  25.15
59  2010-03-01  72.79  -5.38
60  2010-03-02  44.60 124.77
61  2010-03-03 146.06  67.00
62  2010-03-04  48.43  40.45
63  2010-03-05  77.59  92.34
64  2010-03-06  51.12  85.46
65  2010-03-07  20.27  25.23
66  2010-03-08  57.55 138.24
67  2010-03-09 -22.20  39.80
68  2010-03-10 108.62  -6.98
69  2010-03-11  56.13  44.22
70  2010-03-12 136.90  58.30
71  2010-03-13  69.02 142.32
72  2010-03-14  21.60  54.23
73  2010-03-15  74.43  68.28
74  2010-03-16  12.64  46.91
75  2010-03-17  -0.15  36.64
76  2010-03-18  61.66  48.61
77  2010-03-19  32.27  81.51
78  2010-03-20  50.04 133.01
79  2010-03-21  52.97  91.10
80  2010-03-22  26.42  98.32
81  2010-03-23  27.25   0.75
82  2010-03-24  44.59  89.36
83  2010-03-25  97.12  58.80
84  2010-03-26 -10.94  -8.69
85  2010-03-27  73.76  70.84
86  2010-03-28  63.32  43.65
87  2010-03-29  92.52 108.58
88  2010-03-30  37.83  19.36
89  2010-03-31  64.80  32.79
90  2010-04-01  60.68  12.96
91  2010-04-02  28.30  42.92
92  2010-04-03  98.31  66.08
93  2010-04-04  96.42  20.73
94  2010-04-05  78.01  83.21
95  2010-04-06 113.47   1.68
96  2010-04-07  72.34   8.08
97  2010-04-08  -1.06 107.65
98  2010-04-09  27.07   9.37
99  2010-04-10   1.02  66.48
100 2010-04-11  31.06  34.76

把数据进行可视化,可以更直观地理解数据本身。


# 加载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

01

上图中,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')

02

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

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
2016-02-01 09:08:00  35840  35880  -40
2016-02-01 09:09:00  35790  35840  -50
2016-02-01 09:10:00  35800  35840  -40
2016-02-01 09:11:00  35790  35830  -40
2016-02-01 09:12:00  35820  35860  -40
2016-02-01 09:13:00  35810  35850  -40
2016-02-01 09:14:00  35790  35830  -40
2016-02-01 09:15:00  35780  35830  -50
2016-02-01 09:16:00  35770  35810  -40
2016-02-01 09:17:00  35760  35820  -60
2016-02-01 09:18:00  35750  35800  -50
2016-02-01 09:19:00  35760  35810  -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)

画出价差分布柱状图
03

我们假设以-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
14 2016-02-01 10:37:00 35720 35800  -80 -63 -45    -75 kb
6  2016-02-01 10:42:00 35740 35790  -50 -63 -45    -75 ps
15 2016-02-01 11:20:00 35700 35780  -80 -63 -45    -75 kb
7  2016-02-01 11:21:00 35710 35750  -40 -63 -45    -75 ps
24 2016-02-01 11:21:00 35710 35750  -40 -63 -45    -75 ks
4  2016-02-01 11:23:00 35690 35760  -70 -63 -45    -75 pb
16 2016-02-01 11:29:00 35690 35770  -80 -63 -45    -75 kb
8  2016-02-01 13:36:00 35660 35720  -60 -63 -45    -75 ps
17 2016-02-01 13:45:00 35660 35740  -80 -63 -45    -75 kb
9  2016-02-01 13:46:00 35670 35730  -60 -63 -45    -75 ps
18 2016-02-01 13:52:00 35650 35730  -80 -63 -45    -75 kb
10 2016-02-01 13:53:00 35650 35710  -60 -63 -45    -75 ps
19 2016-02-01 13:56:00 35640 35720  -80 -63 -45    -75 kb
11 2016-02-01 14:49:00 35600 35660  -60 -63 -45    -75 ps
20 2016-02-01 14:52:00 35610 35700  -90 -63 -45    -75 kb
12 2016-02-01 14:58:00 35610 35690  -80 -63 -45    -75 ps

数据解释:

  • 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
2016-02-01 10:02:00  cu1605 ks 35710   1 8.9275 178550 26782.5      NA 173651.3
2016-02-01 10:07:00  cu1605 pb 35680   0 8.9200      0     0.0     150 173753.4
2016-02-01 10:37:00  cu1605 kb 35720   1 8.9300 178600 26790.0      NA 173758.1
2016-02-01 10:42:00  cu1605 ps 35740   0 8.9350      0     0.0     100 173780.2
2016-02-01 11:20:00  cu1605 kb 35700   1 8.9250 178500 26775.0      NA 173887.3
2016-02-01 11:21:00  cu1605 ps 35710   0 8.9275      0     0.0      50 173859.4
2016-02-01 11:21:001 cu1605 ks 35710   1 8.9275 178550 26782.5      NA 174044.1
2016-02-01 11:23:00  cu1605 pb 35690   0 8.9225      0     0.0     100 174096.2
2016-02-01 11:29:00  cu1605 kb 35690   1 8.9225 178450 26767.5      NA 174173.3
2016-02-01 13:36:00  cu1605 ps 35660   0 8.9150      0     0.0    -150 173945.5
2016-02-01 13:45:00  cu1605 kb 35660   1 8.9150 178300 26745.0      NA 174260.1
2016-02-01 13:46:00  cu1605 ps 35670   0 8.9175      0     0.0      50 174232.3
2016-02-01 13:52:00  cu1605 kb 35650   1 8.9125 178250 26737.5      NA 174331.9
2016-02-01 13:53:00  cu1605 ps 35650   0 8.9125      0     0.0       0 174254.1
2016-02-01 13:56:00  cu1605 kb 35640   1 8.9100 178200 26730.0      NA 174403.8
2016-02-01 14:49:00  cu1605 ps 35600   0 8.9000      0     0.0    -200 174125.9
2016-02-01 14:52:00  cu1605 kb 35610   1 8.9025 178050 26707.5      NA 174490.6
2016-02-01 14:58:00  cu1605 ps 35610   0 8.9025      0     0.0       0 174405.3

$x2
                       code op price pos    fee  value  margin balance     cash
2016-02-01 09:00:00  cu1606 kb 35900   1 8.9750 179500 26925.0      NA 146147.1
2016-02-01 09:25:00  cu1606 ps 35810   0 8.9525      0     0.0    -450 200214.2
2016-02-01 09:40:00  cu1606 kb 35730   1 8.9325 178650 26797.5      NA 146631.3
2016-02-01 09:47:00  cu1606 ps 35770   0 8.9425      0     0.0     200 200328.4
2016-02-01 10:00:00  cu1606 ks 35770   1 8.9425 178850 26827.5      NA 146715.6
2016-02-01 10:01:00  cu1606 pb 35760   0 8.9400      0     0.0      50 200442.7
2016-02-01 10:02:00  cu1606 kb 35750   1 8.9375 178750 26812.5      NA 146829.8
2016-02-01 10:07:00  cu1606 ps 35750   0 8.9375      0     0.0       0 200557.0
2016-02-01 10:37:00  cu1606 ks 35800   1 8.9500 179000 26850.0      NA 146899.1
2016-02-01 10:42:00  cu1606 pb 35790   0 8.9475      0     0.0      50 200671.2
2016-02-01 11:20:00  cu1606 ks 35780   1 8.9450 178900 26835.0      NA 147043.4
2016-02-01 11:21:00  cu1606 pb 35750   0 8.9375      0     0.0     150 200835.5
2016-02-01 11:21:001 cu1606 kb 35750   1 8.9375 178750 26812.5      NA 147222.6
2016-02-01 11:23:00  cu1606 ps 35760   0 8.9400      0     0.0      50 200949.8
2016-02-01 11:29:00  cu1606 ks 35770   1 8.9425 178850 26827.5      NA 147336.9
2016-02-01 13:36:00  cu1606 pb 35720   0 8.9300      0     0.0     250 201014.1
2016-02-01 13:45:00  cu1606 ks 35740   1 8.9350 178700 26805.0      NA 147446.2
2016-02-01 13:46:00  cu1606 pb 35730   0 8.9325      0     0.0      50 201078.4
2016-02-01 13:52:00  cu1606 ks 35730   1 8.9325 178650 26797.5      NA 147525.5
2016-02-01 13:53:00  cu1606 pb 35710   0 8.9275      0     0.0     100 201142.7
2016-02-01 13:56:00  cu1606 ks 35720   1 8.9300 178600 26790.0      NA 147604.8
2016-02-01 14:49:00  cu1606 pb 35660   0 8.9150      0     0.0     300 201207.0
2016-02-01 14:52:00  cu1606 ks 35700   1 8.9250 178500 26775.0      NA 147706.7
2016-02-01 14:58:00  cu1606 pb 35690   0 8.9225      0     0.0      50 201221.4

数据解释:

  • $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

最后,通过可视化输出交易信号。

04

图例解释

  • 棕色线,为价差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 
6  2016-02-15   1191  0.022    1550 359    1.00  10    0       250     100       155     NaN 
7  2016-02-16     78  0.001     150  72    1.00   1    0       150       0       150     NaN  
8  2016-02-17    179  0.003     250  71    1.00   2    0       200      50       125     NaN 
9  2016-02-18     14  0.000      50  36    1.00   1    0        50      50        50     NaN  
10 2016-02-19    -36 -0.001       0  36     NaN   0    0         0       0       NaN     NaN   
11 2016-02-22     64  0.001     100  36    1.00   1    0       100     100       100     NaN 
12 2016-02-23    632  0.012     850 218    1.00   6    0       200     100       142     NaN 
13 2016-02-24    470  0.009     650 180    1.00   4    0       200       0       162     NaN 
14 2016-02-25    114  0.002     150  36    1.00   1    0       150     150       150     NaN 
15 2016-02-26    178  0.003     250  72    1.00   2    0       150     100       125     NaN 
16 2016-02-29    511  0.009     800 289    0.88   7    1       150     -50       121     -50 

数据解释:

  • date,交易日期
  • profit,净收益
  • ret,每日收益率
  • balance,平仓盈亏
  • fee,手续费
  • winRate,胜率
  • win,胜数
  • fail,败数
  • maxProfit,单笔最大盈利
  • maxLoss,单笔最大亏损
  • avgProfit,平均盈利
  • avgLoss,平均亏损

从结果来看,多么开心啊,几乎每天都是赚钱的!!

cu1605和cu1606两个合同是完美地具备均衡关系的两个金融产品,大家常常所说的跨期套利就是基于这个思路实现的。本文介绍的配对交易模型,是统计套利的一个基本模型,原理很简单,当大家都掌握后拼的就是交易速度了。

利用市场的无效性来获取利润,是每个套利策略都在寻找的目标。通过统计方法,我们可以发现市场的无效性,再以对冲的操作方式,规避绝大部分的市场风险,等待市场的自我修复后来赚钱利润。说起来很简单,但市场的无效性,可能会在极短时间内就被修复。

“天下武功为快不破”,通过量化的手段,让计算机来发现机会,进行交易,实现收益。一切就和谐了!!

转载请注明出处:
http://blog.fens.me/finance-pairs-trading/

打赏作者

R语言构建追涨杀跌量化交易模型

用IT技术玩金融系列文章,将介绍如何使用IT技术,处理金融大数据。在互联网混迹多年,已经熟练掌握一些IT技术。单纯地在互联网做开发,总觉得使劲的方式不对。要想靠技术养活自己,就要把技术变现。通过“跨界”可以寻找新的机会,创造技术的壁垒。

金融是离钱最近的市场,也是变现的好渠道!今天就开始踏上“用IT技术玩金融”之旅!

关于作者:

  • 张丹(Conan), 程序员Java,R,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/finance-chase-sell/

chaseSell

前言

久经股市的老股民,通常都会使用一种常见的交易策略,追涨杀跌交易法。追涨杀跌法,是股市操作的一个重要技巧,就是在股市上涨时买入股票,股市下跌时卖出股票。如果操作得当是很好的赢利手段,在中国股市2015年上半年的牛市中,追涨杀跌交易法就是交易神器法门。

目录

  1. 什么是追涨杀跌?
  2. 追涨杀跌的建型和实现
  3. 模型优化

1. 什么是追涨杀跌?

追涨杀跌是金融市场的专业术语,是金融技术派操盘的一种方式,与抄底摸顶的操作方式刚好相反。追涨杀跌的操作方法是,金融市场中在金融产品(股票,期货,外汇等)价格上涨的时候买入,以期待涨得更多,并以更高的价格卖出获利;在价格下跌的时候卖出进行止损,不管之前金融产品买入的价格是多少,都立刻卖出,以避免更大的损失。

下面以平安银行(000001)股票日K线图为例,截取2005年到2015年7月的股票数据,股价为向前复权的价格。

01

上图中有3条曲线,黑色线是平安银行向前复权后的每日股价,红色线为20日均线,蓝色线为60日均线。关于均线的介绍,请参考文章 两条均线打天下。图中还有一条红色的水平线虚线,是这10年的股价平均值等于7.14元。这10年间,平安银行股价起起落落,每次上涨都会持续一段时间,那么在我们可以上涨中段追入买进,在大幅下跌之前卖出,就是追涨杀跌法。

追涨

追涨通常有2种情况,短线追涨和中线追涨。以股票交易为例。

  • 短线追涨:当天股价已经涨了5个点或更多,这时追进去买入,期待当天收盘涨停,等下一个交易日高价卖出,这就是短线追涨。
  • 中线追涨:某只股票的股价已经上涨了一段时间,走势很强,底部上涨已经有30%或更多,这个时追进去买入,期待后势持续上涨,是中线追涨。

不论短线追涨还是中线追涨,我们追涨的理由和判断的标准都是一样的,“涨得好的股票说明走势很强,那么追强势股期待股票持续上涨”。但从另一个角度看,追涨的风险也是很大的,关键在于我们不知道它能涨到什么时候,到什么位置可能会回调,什么时候是庄家出货。所以说追涨技巧性很强,什么情况下能追,什么情况下不能追,都是很有讲究的。

杀跌

杀跌也是一门较深的操作技巧,杀得好的话既可以回避风险。如果不能进行杀跌的准确判断,则很可能会发生硬性止损,导致严重的亏损。

采取杀跌战术,必须掌握以下操作要点:

  • 杀跌的重要前提是知变,就是对消息、对量能、对周边各种因素的影响有一个准确的、快速的知变过程。
  • 杀跌要善于观察盘面、形态、消息、舆论等方面的细微变化,在对大盘作出准确估量的基础上,迅速作出反应就是确定卖出哪支股票,卖出多少等。
  • 杀跌要善于思考大盘与目标股之问的正向、反向的相关性,及目标股的能量能等方向的关系,从中找出变动的规律。
  • 当市场利好消息接踵而至时,应随时准备卖出股票。当利好出尽就会转向利空,往往消息明朗时,股价早已下跌大半,导致措手不及。

杀跌的后续操作就比较简单了,只需保持良好的心态,耐心等待见底信号后再抄底。一般只有在底部成功接回股票或换股才算成功的杀跌,否则杀跌会失去意义。应保持耐心,千万不要在股价反弹时贸然介入,否则屡买屡跌、会加大损失,应待股价反转时再介入。

追涨杀跌的技巧

要观大势,看清整个市场多空对决的优势一方是谁。

追涨杀跌的假设条件:

  • 市场处于强势交易格局。强势格局赋予个股良好的运作基础;相反,如果市场处于平衡状态,就不适合进行追涨杀跌,更适合高抛低吸。
  • 市场形成了鲜明并有持续性上涨能力的热点或板块效应。

追涨操作的对象:

  • 市场形成鲜明的可持续性的热点时,可追涨这个热点。从理论上讲,只要把握热点板块就能获利,追涨时应重点关注龙头企业。比如:沪深300指数的成分股,就是不错的选择。
  • 形成明显的上涨趋势的个股可以考虑追涨。个股突破重要的压力线,如头肩底形态的颈线位、整理箱体的箱顶位、三角形的上边眼、长期下降趋势线等,待回抽确认其突破的有效性后,可考虑追涨。

追涨杀跌的注意事项:

  • 追涨时应设好止盈位和止损位,确定自己的大致盈利目标。
  • 追涨杀跌应该注意频率的减少,过于频繁的操作,一旦失误增多会严重影响情绪。
  • 追涨杀跌中要多一分平和的心态,少一些侥幸;多一分理性,少一分感性。

追涨杀跌的市场操作

在弱市中,短线追涨往往容易被套,因为弱市里大部分股票的行情只有一天,当天追得高了,第二天多半没有获利逃出的机会。相反,弱市中,中线追涨比较可取,能够在弱市中保持强势的股票不多,但这种强势反而可以持续。但很多时候,弱市中这样的股票反倒不敢追了。

有些人专门追涨停板,追新股开板,追次新股开板等,对于2015年上半年IPO的新股来说收获颇丰,如暴风科技,中超电缆等,其他的时候可能用这套追涨停策略就行不通了。

001

我自己操作并不会盲目追涨,安全第一,虽然还没有吃过追涨的亏,但由于本人目前还不具备基本面的分析能力,也没有消息的渠道,仅仅从技术指标入手,投机性太重,很容易被庄家玩死的。道理很简单,因为如果追涨了,下跌的空间自然就大了,尤其中线追涨,庄家获利后随时可能出货。这时候就必须要做好杀跌的策略。庄股出货跌个50%太普遍了,而且跌下去就很难再起来,这种时候不杀跌就等于自杀。所以,追涨之前一定要想清楚,务必先设好止损位,不能只憧憬获利的美妙。

杀跌也有讲究,不是一跌就杀,震荡、洗盘是很常见的,问题在于正确地区别震仓与出货是很难的,所以很多人是硬性止损,即预先设定价位或百分比,到这个位置就坚决出局,不管它是震仓还是庄家出货。这个时候如果没有基本面的判断或第一手消息的来源,就容易被震荡出局。当然,和不确定的风险相比,我其实更愿意早点出来。

2. 追涨杀跌的建型和实现

既然我们要进行追涨杀跌的操作,就要定义什么是追涨,什么又是杀跌,需要把追涨杀跌的概念量化出来,从而进行建模和实现。对于短线追涨策略可以简单粗暴地选择当日涨幅超过5%的股票进行买入,第二日开盘卖出或到止损位卖出,并没有太多的技术细节,再计算一下胜率概率,就能知道我们要不要短线追涨。

下文我们将以中线追涨为例,进行建模和程序实现。

那么对于中线追涨的情况,我们核心的策略思路就可以转化为,当股价(成交量)向上突破最近20日最高价格(量)时买入,当股价向下突破最近10日最低价格卖出,并以沪深300成分股做为股票交易的标的。其中,为什么是20日最高和10日最低都是经验值,可以做为模型参数进行训练和优化。选择沪深300成分股为标的,是考虑到这些股票都是各个板块的强势股或龙头股,要符合追涨杀跌的假设条件。

计算公式:


20日最高价 = max(T日股价,T-1日股价 ... , T-19日股价)
20日最高量 = max(T日成交量,T-1日成交量 ... , T-19日成交量)
10日最低价 = min(T日股价,T-1日股价 ... , T-9日股价)

当股价大于等于20日最高价时作为买入信号点,当股价小于等于10日最低价时作为卖出信号点。这样我们就把追涨杀跌的投资理论,变成了一个数学模型。

接下来,我们利用R语言对股票数据的进行操作,来实现一个追涨杀跌模型的实例,从而验证我的们投资理论,是否能发现赚钱的机会。

2.1 数据准备

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

我本次用到的数据是从 况客 直接导出的,况客 会提供各种类型的金融数据API,让开发者可以免费下载。当然,你也可以用quantmod包从Yahoo财经下载数据。

本文用到的数据,包括A股日K线(向前复权)数据,从2014年7月到2015年日8月,以CSV格式保存到本地文件stock.csv。

数据格式如下:


000001.SZ,2014-07-02,8.14,8.18,8.10,8.17,28604171
000002.SZ,2014-07-02,8.09,8.13,8.05,8.12,40633122
000004.SZ,2014-07-02,13.9,13.99,13.82,13.95,1081139
000005.SZ,2014-07-02,2.27,2.29,2.26,2.28,4157537
000006.SZ,2014-07-02,4.57,4.57,4.50,4.55,5137384
000010.SZ,2014-07-02,6.6,6.82,6.5,6.73,9909143

一共7列:

  • 第1列,股票代码,code,000001.SZ
  • 第2列,交易日期,date,2014-07-02
  • 第3列,开盘价,Open,8.14
  • 第4列,最高价,High,8.18
  • 第5列,最低价,Low,8.10
  • 第6列,收盘价,Close,8.17
  • 第7列,交易量,Volume,28604171

通过R语言加载股票数据,由于数据所有股票都是混合在一起的,而进行计算时又需要按每支票股计算,所以在数据加载时我就进行了转换,按股票代码进行分组,生成R语言的list对象,同时把每支股票的data.frame类型对象转成XTS时间序列类型对象,方便后续的数据处理。


#加载工具包
> library(plyr)
> library(xts)
> library(TTR)
> library(ggplot2)
> library(scales)

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

# 加载数据
> data<-read("stock.csv")

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

# 查看数据的索引值
> head(names(data))
[1] "000001.SZ" "000002.SZ" "000004.SZ" "000005.SZ" "000006.SZ" "000007.SZ"

# 查看包括的股票数量
> length(data)
[1] 2782

# 获得时间范围
dateArea<-function(sDate=Sys.Date()-365,eDate= Sys.Date(),before=0){  #开始日期,结束日期,提单开始时
  if(class(sDate)=='character') sDate=as.Date(sDate)
  if(class(eDate)=='character') eDate=as.Date(eDate)  
  return(paste(sDate-before,eDate,sep="/"))
}

# 查看股票000001.SZ
> head(data[['000001.SZ']])
               Open     High      Low    Close   Volume
2014-07-02 8.146949 8.180000 8.105636 8.171737 28604171
2014-07-03 8.171737 8.254364 8.122162 8.229576 44690486
2014-07-04 8.237838 8.270889 8.146949 8.188263 34231126
2014-07-07 8.188263 8.204788 8.097374 8.146949 34306164
2014-07-08 8.130424 8.204788 8.072586 8.204788 34608702
2014-07-09 8.196525 8.196525 7.915596 7.973434 58789114

把数据准备好了,我们就可以来建立模型了。

2.2 追涨杀跌模型

为了能拉近我们对市场的了解,我们取从2015年1月1日开始的数据,来创建追涨杀跌的模型。以乐视网(300104)的为例,画出乐视网自2015年以来的每日收盘价,20日最高价和10日最低价。乐视网作为创业板标杆的上市公司,在2015年7月首次被纳入沪深300指数。


# 日K线数据
> title<-'300104.SZ'
> stock<-data[[title]]                                # 获得股票数据
> sDate<-as.Date("2015-01-01")                        # 开始日期
> eDate<-as.Date("2015-08-24")                        # 结束日期
> cdata<-stock[dateArea(sDate,eDate,360)]$Close       # 获得收盘价
> vdata<-stock[dateArea(sDate,eDate,360)]$Volume      # 获得交易量

# 收盘价
> names(cdata)<-"Value"   # 重置列名
> tail(cdata)
           Value
2015-08-14 49.81
2015-08-17 48.30
2015-08-18 45.57
2015-08-19 46.98
2015-08-20 45.79
2015-08-21 42.14
 
# 交易量
> tail(vdata)
             Volume
2015-08-14 42108324
2015-08-17 35939096
2015-08-18 52745702
2015-08-19 43447844
2015-08-20 32916746
2015-08-21 34802494

定义画图函数drawLine(),支持画出多条曲线,包括收盘价,最高价,最低价。


# 画图函数
> drawLine<-function(cdata,titie="Stock",sDate=min(index(cdata)),eDate=max(index(cdata)),breaks="1 year"){
+     if(sDate<min(index(cdata))) sDate=min(index(cdata))
+     if(eDate>max(index(cdata))) eDate=max(index(cdata))  
+     cdata<-na.omit(cdata)
+     
+     g<-ggplot(aes(x=Index, y=Value),data=fortify(cdata[,1],melt=TRUE))
+     g<-g+geom_line()
+     
+     if(ncol(cdata)>1){ # 多条线
+         g<-g+geom_line(aes(colour=Series),data=fortify(cdata[,-1],melt=TRUE))  
+     }
+     
+     g<-g+scale_x_date(labels=date_format("%Y-%m"),breaks=date_breaks(breaks),limits = c(sDate,eDate))
+     g<-g+ylim(min(cdata$Value), max(cdata$Value))
+     g<-g+xlab("") + ylab("Price")+ggtitle(title)
+     g
+ }

# 画出收盘价
# drawLine(cdata,title,sDate,eDate,'1 month')    # 画图

# 计算最近20日的最高价和10日的最低价
> minmax<-function(data,max=20,min=10){
+   d1<-na.locf(data,fromLast=TRUE)
+   d2<-merge(d1,min=runMin(d1,min),max=runMax(d1,max))
+   return(d2[,-1])
+ }
 
# 画出股价,最高价和最低价
> ldata<-cbind(cdata,minmax(cdata))
> drawLine(ldata,title,sDate,eDate,'1 month')    # 画图

01

上图中有3条线,黑色线为乐视网的每日收盘价,蓝色线为最近20日最高价,红色线为最近10日最低价。

根据模型的计算公式,我们计算买入信号,当股价向上突破最近20日最高价格时买入。


# 买入信号函数
> buyPoint<-function(ldata){   
+  idx<-which(ldata$Value == ldata$max)
+  return(ldata[idx,])                                  
+ }

# 计算买入的点
> buydata<-buyPoint(ldata)
> buydata
              Value      min      max
2015-01-08 17.43721 13.70164 17.43721
2015-01-09 17.98709 13.74254 17.98709
2015-01-12 19.53222 13.74254 19.53222
2015-01-15 20.21389 14.74232 20.21389
2015-01-16 22.23619 16.08749 22.23619
2015-01-19 23.04056 16.36016 23.04056
2015-01-20 23.89947 16.36016 23.89947
2015-01-26 24.77656 19.22774 24.77656
2015-01-27 25.16284 19.40043 25.16284
2015-02-05 26.91247 21.99533 26.91247
2015-02-10 28.68482 21.99533 28.68482
2015-02-11 31.55239 21.99533 31.55239
2015-02-12 31.87960 21.99533 31.87960
2015-02-13 35.06983 22.72245 35.06983
2015-02-16 38.57817 24.22213 38.57817
2015-02-17 40.99130 24.46753 40.99130
2015-03-16 41.07764 34.32453 41.07764
2015-03-18 41.94564 34.32453 41.94564
2015-03-24 45.34946 37.17393 45.34946
2015-04-23 46.27199 37.06031 46.27199
2015-04-24 50.89829 37.06031 50.89829
2015-04-27 50.90283 37.06031 50.90283
2015-04-28 55.44277 37.06031 55.44277
2015-04-29 60.98705 37.06031 60.98705
2015-05-06 62.25497 45.19495 62.25497
2015-05-07 66.20413 46.27199 66.20413
2015-05-08 67.23573 50.89829 67.23573
2015-05-11 73.96157 50.90283 73.96157
2015-05-12 81.36000 55.44277 81.36000
2015-05-13 82.49000 57.16514 82.49000

画出买入的信号图,让我们可以直观的看到效果。


# 画图函数
> drawPoint<-function(ldata,pdata,titie,sDate,eDate,breaks="1 year"){
+     ldata<-na.omit(ldata)
+     g<-ggplot(aes(x=Index, y=Value),data=fortify(ldata[,1],melt=TRUE))
+     g<-g+geom_line()
+     g<-g+geom_line(aes(colour=Series),data=fortify(ldata[,-1],melt=TRUE))
+     
+     if(is.data.frame(pdata)){
+         g<-g+geom_point(aes(x=Index,y=Value,colour=op),data=pdata,size=4)
+     }else{
+         g<-g+geom_point(aes(x=Index,y=Value,colour=Series),data=na.omit(fortify(pdata,melt=TRUE)),size=4)  
+     }
+     g<-g+scale_x_date(labels=date_format("%Y-%m"),breaks=date_breaks(breaks),limits = c(sDate,eDate))
+     g<-g+xlab("") + ylab("Price")+ggtitle(title)
+     g
+ }

> drawPoint(ldata,buydata$Value,title,sDate,eDate,'1 month')  # 画图

02

如上图所示,蓝色的点为股价大于等于最近20日最高价的点,作为买入信号。所有买入信号点,都是出现在单边上行的牛势中,对于2015年上半年以来的行情来说,追涨的信号会被大量触发。

接下来,我们继续计算卖出信号点,当股价小于等于最近10日最低价时作为卖出信号点。


> # 计算卖出的信号点
> stopPoint<-function(ldata,buydata){  
+     idx<-which(ldata$Value == ldata$min)
+     idx<-idx[which(c(0,diff(idx))!=1)]   # 第一点用0表示
+     
+     selldata<-ldata[idx,]               # 所有低于最小值的点  
+     idx2<-sapply(index(buydata),function(e){  # 买后的卖点
+         head(which(index(selldata)>e),1)
+     })
+     
+     return(selldata[unique(idx2),])
+ } 

# 卖出信号
> selldata<-stopPoint(ldata,buydata)
> selldata
              Value      min      max
2015-01-30 21.99533 21.99533 25.16284
2015-03-06 34.32453 34.32453 40.99130
2015-04-08 38.01011 38.01011 45.34946
2015-05-28 64.68000 64.68000 82.49000

一共有4笔卖出信号,为了让数据更加直观,我们合并买入信号和卖出信号,进行画图可视化。


# 买卖信号,画图
> bsdata<-merge(buydata$Value,selldata$Value)
> names(bsdata)<-c("buy","sell")
> drawPoint(ldata,bsdata,title,sDate,eDate,'1 month') #画图

03

上图中,紫色点为卖出信号点,红色点为买入信号点。我们可以很明显的看出,如果根据交易信号在红色点买入,紫色点卖出,我们是应该赚钱的。那么具体赚了多少呢,我需要计算出来?


> # 合并交易信号
> signal<-function(buy, sell){
+     selldf<-data.frame(sell,op=as.character(rep("S",nrow(sell))))
+     buydf<-data.frame(buy,op=as.character(rep("B",nrow(buy))))
+     sdata<-rbind(buydf,selldf)                                       # 交易信号数据
+     sdata[order(as.Date(row.names(sdata))),]
+ }

# 合并交易信号
> sdata<-signal(buydata,selldata)                                   
> sdata
              Value      min      max op
2015-01-08 17.43721 13.70164 17.43721  B
2015-01-09 17.98709 13.74254 17.98709  B
2015-01-12 19.53222 13.74254 19.53222  B
2015-01-15 20.21389 14.74232 20.21389  B
2015-01-16 22.23619 16.08749 22.23619  B
2015-01-19 23.04056 16.36016 23.04056  B
2015-01-20 23.89947 16.36016 23.89947  B
2015-01-26 24.77656 19.22774 24.77656  B
2015-01-27 25.16284 19.40043 25.16284  B
2015-01-30 21.99533 21.99533 25.16284  S
2015-02-05 26.91247 21.99533 26.91247  B
2015-02-10 28.68482 21.99533 28.68482  B
2015-02-11 31.55239 21.99533 31.55239  B
2015-02-12 31.87960 21.99533 31.87960  B
2015-02-13 35.06983 22.72245 35.06983  B
2015-02-16 38.57817 24.22213 38.57817  B
2015-02-17 40.99130 24.46753 40.99130  B
2015-03-06 34.32453 34.32453 40.99130  S
2015-03-16 41.07764 34.32453 41.07764  B
2015-03-18 41.94564 34.32453 41.94564  B
2015-03-24 45.34946 37.17393 45.34946  B
2015-04-08 38.01011 38.01011 45.34946  S
2015-04-23 46.27199 37.06031 46.27199  B
2015-04-24 50.89829 37.06031 50.89829  B
2015-04-27 50.90283 37.06031 50.90283  B
2015-04-28 55.44277 37.06031 55.44277  B
2015-04-29 60.98705 37.06031 60.98705  B
2015-05-06 62.25497 45.19495 62.25497  B
2015-05-07 66.20413 46.27199 66.20413  B
2015-05-08 67.23573 50.89829 67.23573  B
2015-05-11 73.96157 50.90283 73.96157  B
2015-05-12 81.36000 55.44277 81.36000  B
2015-05-13 82.49000 57.16514 82.49000  B
2015-05-28 64.68000 64.68000 82.49000  S

接下来,我们利用交易信号数据,进行模拟交易。我们设定交易参数和规则:

  • 以10万元人民币为本金。
  • 买入信号出现时,以收盘价买入,每次买入价值1万元的股票。如果连续出现买入信号,则一直买入。若现金不足1万元时,则跳过买入信号。
  • 卖出信号出现时,以收盘价卖出,一次性平仓信号对应的股票。
  • 手续费为0元

下面我们进行模拟交易。


> # 模拟交易
> trade<-function(sdata,capital=100000,fixMoney=10000){ # 交易信号,总资金,每次定投资金
+     amount<-0
+     cash<-capital
+     
+     ticks<-data.frame()
+     for(i in 1:nrow(sdata)){
+         row<-sdata[i,]
+         if(row$op=='B'){
+             if(cash<fixMoney){
+                 print(paste(row.names(row),"No enough cash"))
+                 next
+             }
+             amount0<-floor(fixMoney/row$Value) # 本次交易量
+             amount<-amount+amount0
+             cash<-cash-amount0*row$Value
+         }
+         
+         if(row$op=='S'){
+             cash<-cash+amount*row$Value
+             amount<-0
+         }
+         
+         row$cash<-round(cash,2)
+         row$amount<-amount
+         row$asset<-round(cash+amount*row$Value,2)
+         ticks<-rbind(ticks,row)
+     }
+     
+     
+     ticks$diff<-c(0,round(diff(ticks$asset),2))
+     
+     rise<-ticks[intersect(which(ticks$diff>0),which(ticks$op=='S')),]   # 赚钱的交易
+     fall<-ticks[intersect(which(ticks$diff<0),which(ticks$op=='S')),]   # 赔钱的交易
+     
+     return(list(
+         ticks=ticks,
+         rise=rise,
+         fall=fall
+     ))
+ }

# 交易结果
> result<-trade(sdata,100000,10000)  

来看一下,每笔交易的明细。


> result$ticks
              Value      min      max op      cash amount    asset      diff
2015-01-08 17.43721 13.70164 17.43721  B  90008.48    573 100000.0      0.00
2015-01-09 17.98709 13.74254 17.98709  B  80025.65   1128 100315.1    315.08
2015-01-12 19.53222 13.74254 19.53222  B  70044.68   1639 102058.0   1742.91
2015-01-15 20.21389 14.74232 20.21389  B  60059.02   2133 103175.2   1117.26
2015-01-16 22.23619 16.08749 22.23619  B  50074.97   2582 107488.8   4313.56
2015-01-19 23.04056 16.36016 23.04056  B  40075.37   3016 109565.7   2076.90
2015-01-20 23.89947 16.36016 23.89947  B  30085.39   3434 112156.2   2590.46
2015-01-26 24.77656 19.22774 24.77656  B  20100.44   3837 115168.1   3011.92
2015-01-27 25.16284 19.40043 25.16284  B  10110.79   4234 116650.2   1482.16
2015-01-30 21.99533 21.99533 25.16284  S 103239.02      0 103239.0 -13411.23
2015-02-05 26.91247 21.99533 26.91247  B  93254.49    371 103239.0      0.00
2015-02-10 28.68482 21.99533 28.68482  B  83272.17    719 103896.6    657.54
2015-02-11 31.55239 21.99533 31.55239  B  73301.62   1035 105958.3   2061.78
2015-02-12 31.87960 21.99533 31.87960  B  63323.30   1348 106297.0    338.66
2015-02-13 35.06983 22.72245 35.06983  B  53328.40   1633 110597.4   4300.43
2015-02-16 38.57817 24.22213 38.57817  B  43336.66   1892 116326.6   5729.13
2015-02-17 40.99130 24.46753 40.99130  B  33375.77   2135 120892.2   4565.63
2015-03-06 34.32453 34.32453 40.99130  S 106658.65      0 106658.6 -14233.54
2015-03-16 41.07764 34.32453 41.07764  B  96676.78    243 106658.6      0.00
2015-03-18 41.94564 34.32453 41.94564  B  86693.72    481 106869.6    210.92
2015-03-24 45.34946 37.17393 45.34946  B  76716.83    701 108506.8   1637.24
2015-04-08 38.01011 38.01011 45.34946  S 103361.92      0 103361.9  -5144.89
2015-04-23 46.27199 37.06031 46.27199  B  93367.17    216 103361.9      0.00
2015-04-24 50.89829 37.06031 50.89829  B  83391.11    412 104361.2    999.28
2015-04-27 50.90283 37.06031 50.90283  B  73414.15    608 104363.1      1.87
2015-04-28 55.44277 37.06031 55.44277  B  63434.45    788 107123.4   2760.29
2015-04-29 60.98705 37.06031 60.98705  B  53493.56    951 111492.2   4368.89
2015-05-06 62.25497 45.19495 62.25497  B  43532.77   1111 112698.0   1205.79
2015-05-07 66.20413 46.27199 66.20413  B  33535.95   1262 117085.6   4387.51
2015-05-08 67.23573 50.89829 67.23573  B  23585.06   1410 118387.4   1301.88
2015-05-11 73.96157 50.90283 73.96157  B  13600.25   1545 127870.9   9483.44
2015-05-12 81.36000 55.44277 81.36000  B   3674.33   1667 139301.5  11430.58
2015-05-28 64.68000 64.68000 82.49000  S 111495.89      0 111495.9 -27805.56

一共发生了34笔交易,其中30笔买入,4笔卖出。最后,资金剩余111495.9元,赚了11495元,收益率11.5%。

3. 模型优化

我们看到在强势格局的大牛市中,通过追涨能让我们获利颇丰。其实我们可以把模型再进一步优化的,在构建卖出信号时,是以最近10日最低价为卖出点来看,应该还有更好的卖出点可以选择。那么我们就要优化一下模型,比如按下面的优化条件。当股价低于前一个买入点价格的时进行卖出,把小于等于最近10日最低价设为止损点。按照这样的优化策略,我们是不是可以有更大的收益呢?

这样的优化思路,会让我们策略对波动更敏感,更容易被震荡出局;当然好外在于,可以更快的触发止盈和止损条件,牛市中收益更大。


# 优化条件,当股价低于前一个买入点价格时进行卖出,小于10日最低价为止损点。
> # 计算卖出的信号点
> sellPoint<-function(ldata,buydata){
+     
+     arr<-c()
+     for(i in 1:nrow(buydata)){
+         
+         if(i>1){ # 跳转第一个点
+             date<-index(buydata[i,])#;print(date)      
+             
+             # 价格 小于 上一次的买入的价格就卖出
+             last<-as.vector(buydata[i-1,]$Value) # 上一次买入的价格
+             lst<-ldata[paste(date,"/",sep="")]$Value      
+             idx<-head(which(lst < last),1)
+             
+             if(length(idx)>0){        
+                 arr<-rbind(arr,index(lst[idx]))
+             }
+         }
+     }
+     selldata<-ldata[as.Date(unique(arr)),]
+     
+     # 过滤多余的卖出点
+     bsdata<-merge(buydata$Value,selldata$Value)
+     names(bsdata)<-c("buy","Value")
+     idx1<-which(!is.na(bsdata$Value))
+     idx2<-idx1[which(c(0,diff(idx1))==1)]
+     bsdata$Value[idx2]<-NA
+     return(bsdata$Value[which(!is.na(bsdata$Value))])
+     
+ }

# 卖出信号
> selldata<-sellPoint(ldata,buydata)
> selldata
              Value
2015-01-21 22.81788
2015-01-28 23.60408
2015-02-25 36.89217
2015-03-17 39.97333
2015-03-19 40.96858
2015-03-26 39.25985
2015-05-14 74.24000

我们重新生成了,卖出的信号点有7个,比上次多出3个点,画出交易信号的图形。

04

合并买卖的交易信号。


> sdata<-signal(buydata$Value,selldata$Value)                                   # 合并交易信号
> sdata
              Value op
2015-01-08 17.43721  B
2015-01-09 17.98709  B
2015-01-12 19.53222  B
2015-01-15 20.21389  B
2015-01-16 22.23619  B
2015-01-19 23.04056  B
2015-01-20 23.89947  B
2015-01-21 22.81788  S
2015-01-26 24.77656  B
2015-01-27 25.16284  B
2015-01-28 23.60408  S
2015-02-05 26.91247  B
2015-02-10 28.68482  B
2015-02-11 31.55239  B
2015-02-12 31.87960  B
2015-02-13 35.06983  B
2015-02-16 38.57817  B
2015-02-17 40.99130  B
2015-02-25 36.89217  S
2015-03-16 41.07764  B
2015-03-17 39.97333  S
2015-03-18 41.94564  B
2015-03-19 40.96858  S
2015-03-24 45.34946  B
2015-03-26 39.25985  S
2015-04-23 46.27199  B
2015-04-24 50.89829  B
2015-04-27 50.90283  B
2015-04-28 55.44277  B
2015-04-29 60.98705  B
2015-05-06 62.25497  B
2015-05-07 66.20413  B
2015-05-08 67.23573  B
2015-05-11 73.96157  B
2015-05-12 81.36000  B
2015-05-13 82.49000  B
2015-05-14 74.24000  S

一共发生了37笔交易,其中30笔买入,7笔卖出。最后,资金剩余137483.8元,赚了37483元,收益率37.5%。

我们把卖号信号和止损信号,合并画到一张图上。


# 止损信号
> stopdata<-stopPoint(ldata,buydata)

# 合并买卖信号,止损信号
> bsdata<-merge(buydata$Value,selldata$Value,stopdata$Value)
> names(bsdata)<-c("buy","sell","stop")
> drawPoint(ldata,bsdata,title,sDate,eDate,'1 month') #画图

05

图中红色点为买点,蓝色点为优化的卖点,紫色点为止损点。从图中可以非常清楚的看到,蓝色卖出点要优于紫色的止损点。这样就达到了,模型优化的目的了。虽然只是一个很小的优化,就可以给我们带来不错的收益。

接下来,通过况客平台,我来够建一个每日更新的交易列表,实现把静态图变成了动态图。

追涨杀跌策略在牛市中会让我们赚取非常大的利润,那么对于2015年下半年行情,牛市已经不复存在,震荡市会一直持续,那么我们可以使用均值回归策略,发现逆市中的投资机会

最后总结,本文从 追涨杀跌 的思路开始,到市场特征检验,再到数学公式,R语言建模,再到历史数据回测。通过R语言,很简单地就实现了一个我们脑子中的投资想法。类似的投资想法其实谁都有,利用IT人的技术优势,可以真正地与实际操作结合起来。

这样就可以更容易地实现,从IT技术到价值的转变。IT人,加油!

转载请注明出处:
http://blog.fens.me/finance-chase-sell/

打赏作者

均值回归,逆市中的投资机会

用IT技术玩金融系列文章,将介绍如何使用IT技术,处理金融大数据。在互联网混迹多年,已经熟练掌握一些IT技术。单纯地在互联网做开发,总觉得使劲的方式不对。要想靠技术养活自己,就要把技术变现。通过“跨界”可以寻找新的机会,创造技术的壁垒。

金融是离钱最近的市场,也是变现的好渠道!今天就开始踏上“用IT技术玩金融”之旅!

关于作者:

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/finance-mean-reversion/

meanReversion

前言

在股票市场中有两种典型的投资策略:趋势追踪(Trend Following) 和 均值回归(Mean Reversion)。 趋势追踪策略的特点在大行情的波动段找到有效的交易信号,不仅简单而且有效,我之前写的一篇文章 两条均线打天下 就属于趋势追踪策略。而均值回归策略则是一种反趋势策略,一波大幅上涨后容易出现下跌,而一波大幅下跌后容易出现上涨。其特点在振荡的在震荡的市场中非常有效,捕捉小的机会,本文就将介绍这种策略。

目录

  1. 均值回归原理
  2. 均值回归模型和实现
  3. 量化选股

1. 均值回归原理

在金融学中,均值回归是价格偏离均衡价格水平一定程度后向均衡价格靠拢的规律。本质上,均值回归就是哲学思想中所说的物极必反,可以简单地概括为“涨多必跌,跌多必涨”的规律。

均值回归是指股票价格无论高于或低于均值(均衡价格水平)都会以很高的概率向均值回归。根据这个理论,股票价格总是围绕其均值上下波动。一种上涨或者下跌的趋势不管其延续的时间多长都不能永远持续下去,最终均值回归的规律一定会出现:涨得太多了,就会向均值移动下跌;跌得太多了,就会向均值移动上升。如果我们认为事物总要回归常态,并且基于这样的预期来做任何决策的时候,我们就是在应用均值回归的理论。

下面以平安银行(000001)股票日K线图为例,可以非常直观的了解均值回归这种现象, 截取2005年到2015年7月的股票数据,股价为向前复权的价格。

01

上图中有3条曲线,黑色线是平安银行向前复权后的每日股价,红色线为20日均线,蓝色线为60日均线。关于均线的介绍,请参考文章 两条均线打天下。图中还有一条红色的水平线虚线,是这10年的股价平均值等于7.14元。这10年间,平安银行的股价经历了几波上涨和下跌,多次穿越7.14平均值。那么这个现象就是我们要讨论的均值回归。

1.1 均值回归的3个特性

均值回归是价值投资理论成立的一个核心理论,具有3个特性:必然性、不对称性、政府调控。

必然性,股票价格不能总是上涨或下跌,一种趋势不管其持续的时间多长都不能永远持续下去。在一个趋势内,股票价格呈持续上升或下降,我们称之为均值回避(Mean Aversion)。当出现相反趋势时就呈均值回归(Mean Reversion),但回归的周期有随机性是我们不能预测。不同的股票市场,回归的周期会不一样的,就算是相同的市场,回归的周期也是不一样的。

我们换支股票,以苏宁云商(002024)股票日K线图为例, 同样截取2005年到2015年7月的向前复权的股价数据,如下图所示。我们看到苏宁云商在2006年到2007年有一波大涨随后下跌;从2009到2010年时,第二波大涨;2013年下半年迎来第三波大涨;2014年下半年到2015年第四波大涨。从图形上可以直观看到,2015年这波涨的最急,波动率也是最大的;从现象中,我们可以判断一种趋势不管其持续的时间多长都不能永远持续下去。

02

不对称性,股价波动的幅度与速度是不一样的,回归时的幅度与速度具有随机性。对称的均值回归才是不正常的、偶然的,这一点也也可以从股票中所验证。

我们合并平安银行(000001)和苏宁云商(002024)股票日K线图为例,所下图所示。两支股票在2007年中,都赶上了大的上涨行情,曲线基本吻合。到2008年2支股票都遇到了大跌,但波动率和速度都是不一样的,随后在2010年到2012年出现了完成不一样的走势,无规律可寻,体现了均值回归时的随机性和不对称性。

03

政府行为,股票收益率不会偏离价值均值时间太久,市场的内在力量会促使其向内在价值回归。市场在没有政府政策的作用下,股票价格会在市场机制下自然地向均值回归。但这并不否定政府行为对促进市场有效性的作用,因为市场偏离内在价值后并不等于立即就会向内在价值回归,很可能会出现持续地均值回避。政府行为会起到抑制市场调节市场的作用,是必不可少的因素之一,市场失灵也是政府参与调控的直接的结果。

对于政府政策行为,比如升准、降准、升息、降息,在股市中都会有比如明显的体现。房地产股、银行股,都会受到国家宏观调控的直接的影响。下如所示,在图中增加万科A(0000002)的股票,图中3条线分别是平安银行,万科A,苏宁云商3支股票。我们发现地产和银行的股价走势是比较相近的,而电商的走势是不太一样的。

另外,增加2种颜色的辅助线,红色为升息的时间点和利率变动值,黄色为降息的时间点和利率变动值。当2007年股市超涨的时候,国家宏观调控通过升息鼓励存款,抑制高股价;当股票超跌的时候,通过降息推动投资和消费。2015年金融改革,政府一直都在降息拉动股市。从图中,我们看到万科A和平安银行对于升息和降息的调控是比较明显的,对于苏宁云商就不是特别的明显了。

04

通过对市场的回顾,我们基本验证了均值回归的理论是和市场的行为是一致的。那么,接下来我们应该如何应用这个理论来找到投资的切入点呢?

1.2 计算原理和公式

从价值投资的角度,我们发现股价会在平均值上下波动,但如果考虑到资金的时间成本,把钱都压在股市中,等待几年的大行情,也是很不划算的。那么我们就需要对价值均值进行重新定义,以20日均值来代替长期均值,找到短周期的一种投资方法。

计算原理:取日K线,以N日均线做为均值回归的短期均衡价格水平(均值),计算股价到均值的差值,求出差值的N日的平均标准差,从而判断差值的对于均值的偏离,当偏离超过2倍标准差时,我们就认为股价超涨或超跌,股价会遵循均值回归的理论,向均值不停地进行修复。

计算公式:


N日平均值     =  [T日股价 + (T-1)日股价 + ... + (T-(N-1))日股价]/N
差值          =  N日平均值 - N日股价
N日差值均值   =  [T日差值 + (T-1)日差值 + ... + (T-(N-1))日差值]/N
N日差值标准差 =  sqrt([(T日差值 - T日差值均值)^2 + ... + ((T-(N-1))日差值 - (T-(N-1))日差值均值)^2 ]/N)

如果N为20日,则


20日平均值     =  [T日股价 + (T-1)日股价 + ... + (T-19)日股价]/20

计算偏离点


T日差值 > T日差值标准差 * 2

我们以偏离点作为买入信号点,以均线和股价的下一个交点做为卖出信号点。这样我们就把均值回归的投资理论,变成了一个数学模型。

2. 均值回归模型和实现

接下来,我们利用R语言对股票数据的进行操作,来实现一个均值回归模型的实例,从而验证我的们投资理论,是否能发现赚钱的机会。

2.1 数据准备

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

我本次用到的数据是从 况客 直接导出的,况客 会提供各种类型的金融数据API,让开发者可以免费下载。当然,你也可以用quantmod包从Yahoo财经下载。

本文用到的数据,包括A股日K线(向前复权)数据,从2014年7月到2015年日7月,以CSV格式保存到本地文件stock.csv。

数据格式如下:


000001.SZ,2014-07-02,8.14,8.18,8.10,8.17,28604171
000002.SZ,2014-07-02,8.09,8.13,8.05,8.12,40633122
000004.SZ,2014-07-02,13.9,13.99,13.82,13.95,1081139
000005.SZ,2014-07-02,2.27,2.29,2.26,2.28,4157537
000006.SZ,2014-07-02,4.57,4.57,4.50,4.55,5137384
000010.SZ,2014-07-02,6.6,6.82,6.5,6.73,9909143

一共7列:

  • 第1列,股票代码,code,000001.SZ
  • 第2列,交易日期,date,2014-07-02
  • 第3列,开盘价,Open,8.14
  • 第4列,最高价,High,8.18
  • 第5列,最低价,Low,8.10
  • 第6列,收盘价,Close,8.17
  • 第7列,交易量,Volume,28604171

通过R语言加载股票数据,由于数据所有股票都是混合在一起的,而进行计算时又需要按每支票股计算,所以在数据加载时我就进行了转换,按股票代码进行分组,生成R语言的list对象,同时把每支股票的data.frame类型对象转成XTS时间序列类型对象,方便后续的数据处理。


#加载工具包
> library(plyr)
> library(xts)
> library(TTR)
> library(ggplot2)
> library(scales)

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

# 加载数据
> data<-read("stock.csv")

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

# 查看数据的索引值
> head(names(data))
[1] "000001.SZ" "000002.SZ" "000004.SZ" "000005.SZ" "000006.SZ" "000007.SZ"

# 查看包括的股票数量
> length(data)
[1] 2782

# 查看股票000001.SZ
> head(data[['000001.SZ']])
               Open     High      Low    Close   Volume
2014-07-02 8.146949 8.180000 8.105636 8.171737 28604171
2014-07-03 8.171737 8.254364 8.122162 8.229576 44690486
2014-07-04 8.237838 8.270889 8.146949 8.188263 34231126
2014-07-07 8.188263 8.204788 8.097374 8.146949 34306164
2014-07-08 8.130424 8.204788 8.072586 8.204788 34608702
2014-07-09 8.196525 8.196525 7.915596 7.973434 58789114

把数据准备好了,我们就可以来建立模型了。

2.2 均值回归模型
为了能拉近我们对市场的了解,我们取从2015年1月1日开始的数据,来创建均值回归模型。以平安银行(000001)的为例,画出平安银行的2015年以来的日K线和均线。


# 获得时间范围
> dateArea<-function(sDate=Sys.Date()-365,eDate= Sys.Date(),before=0){  #开始日期,结束日期,提单开始时
+     if(class(sDate)=='character') sDate=as.Date(sDate)
+     if(class(eDate)=='character') eDate=as.Date(eDate)  
+     return(paste(sDate-before,eDate,sep="/"))
+ }
 
# 计算移动平均线
> ma<-function(cdata,mas=c(5,20,60)){
+     if(nrow(cdata)<=max(mas)) return(NULL)
+     ldata<-cdata
+     for(m in mas){
+         ldata<-merge(ldata,SMA(cdata,m))
+     }
+     names(ldata)<-c('Value',paste('ma',mas,sep=''))
+     return(ldata)
+ }

# 日K线和均线
> title<-'000001.SZ'
> SZ000011<-data[[title]]                             # 获得股票数据
> sDate<-as.Date("2015-01-01")                        # 开始日期
> eDate<-as.Date("2015-07-10")                        # 结束日期
> cdata<-SZ000011[dateArea(sDate,eDate,360)]$Close    # 获得收盘价
> ldata<-ma(cdata,c(5,20,60))                         # 选择移动平均指标

# 打印移动平均指标
> tail(ldata)
           Value    ma5    ma20     ma60
2015-07-03 13.07 13.768 15.2545 15.84355
2015-07-06 13.88 13.832 15.1335 15.82700
2015-07-07 14.65 13.854 15.0015 15.79850
2015-07-08 13.19 13.708 14.8120 15.74267
2015-07-09 14.26 13.810 14.6910 15.70867
2015-07-10 14.86 14.168 14.6100 15.67883

我们设置3条移动平均线,分别是5日平均线,20日平均线,60日平均线,当然也可以按照自己的个性要求设置符合自己的周期。画出日K线和均线图。


> drawLine<-function(ldata,titie="Stock_MA",sDate=min(index(ldata)),eDate=max(index(ldata)),breaks="1 year",avg=FALSE,out=FALSE){
+     if(sDate<min(index(ldata))) sDate=min(index(ldata))
+     if(eDate>max(index(ldata))) eDate=max(index(ldata))  
+     ldata<-na.omit(ldata)
+     
+     g<-ggplot(aes(x=Index, y=Value),data=fortify(ldata[,1],melt=TRUE))
+     g<-g+geom_line()
+     g<-g+geom_line(aes(colour=Series),data=fortify(ldata[,-1],melt=TRUE))
+ 
+     if(avg){
+         meanVal<<-round(mean(ldata[dateArea(sDate,eDate)]$Value),2) # 均值
+         g<-g+geom_hline(aes(yintercept=meanVal),color="red",alpha=0.8,size=1,linetype="dashed")
+         g<-g+geom_text(aes(x=sDate, y=meanVal,label=meanVal),color="red",vjust=-0.4)
+     }
+     g<-g+scale_x_date(labels=date_format("%Y-%m"),breaks=date_breaks(breaks),limits = c(sDate,eDate))
+     g<-g+ylim(min(ldata$Value), max(ldata$Value))
+     g<-g+xlab("") + ylab("Price")+ggtitle(title)
+     g
+ }

> drawLine(ldata,title,sDate,eDate,'1 month',TRUE)    # 画图

05

如图所示,60日的移动平均线是最平滑的,5日的移动平均线是波动最大的。5日平均线和股价的交叉,明显多于60日平均线和股价的交叉。那么可以说在相同的时间周期内,短周期的移动平均线,比长周期的移动平均线更具有均值回归的特点。

我们分别计算不同周期的,股价与移动平均线的差值的平均标准差。


> getMaSd<-function(ldata,mas=20,sDate,eDate){}) # ...代码省略

# 5日平均线的差值、平均标准差
> ldata5<-getMaSd(ldata,5,sDate,eDate)
> head(ldata5)
              Value      ma5        dif        sd  rate
2015-01-05 13.23673 12.78724 -0.4494869 0.1613198 -2.79
2015-01-06 13.03842 12.89961 -0.1388121 0.1909328 -0.73
2015-01-07 12.79055 12.99215  0.2016081 0.3169068  0.64
2015-01-08 12.36089 12.90292  0.5420283 0.4472248  1.21
2015-01-09 12.46004 12.77733  0.3172848 0.3910700  0.81
2015-01-12 12.20390 12.57076  0.3668606 0.2533165  1.45


# 20日平均线的差值、平均标准差
> ldata20<-getMaSd(ldata,20,sDate,eDate)
> head(ldata20)
              Value     ma20         dif        sd  rate
2015-01-05 13.23673 12.18613 -1.05059293 0.6556366 -1.60
2015-01-06 13.03842 12.23778 -0.80064848 0.6021093 -1.33
2015-01-07 12.79055 12.24810 -0.54244141 0.4754686 -1.14
2015-01-08 12.36089 12.29975 -0.06114343 0.5130410 -0.12
2015-01-09 12.46004 12.33651 -0.12352626 0.5150453 -0.24
2015-01-12 12.20390 12.37163  0.16773131 0.5531618  0.30


# 60日平均线的差值、平均标准差
> ldata60<-getMaSd(ldata,60,sDate,eDate)
> head(ldata60)
              Value     ma60       dif       sd  rate
2015-01-05 13.23673 10.06939 -3.167340 1.264792 -2.50
2015-01-06 13.03842 10.14678 -2.891644 1.271689 -2.27
2015-01-07 12.79055 10.22087 -2.569677 1.269302 -2.02
2015-01-08 12.36089 10.28752 -2.073368 1.258813 -1.65
2015-01-09 12.46004 10.35527 -2.104766 1.247967 -1.69
2015-01-12 12.20390 10.41821 -1.785691 1.233989 -1.45

5日的平均线的差值和平均标准差是最小的,而60日的平均线的差值和平均标准差是最大的。如果我们以5日移动平均线做为均值时,会频繁进行交易,但每次收益都很小,可能都不够手续费的成本;另一方面,如果我们以60日移动平均线做为均值时,交易次数会较少,但可能会出现股票成形趋势性上涨或下跌,长时间不能回归的情况,可能会造成现金头寸的紧张。综合上面的2种情况,我们可以选择20日均线作为均值的标的。

根据模型的计算公式,当差值超过2倍的平均标准差时,我们认为股价出现了偏离,以偏离点做为模型的买入信号,当均线和股价再次相交时做为卖出信号。

上一步,我们已经计算出了偏离值,并保存在rate列中。下面我们要找到大于2倍标准化差的点,并画图。


# 差值和平均标准差,大于2倍平均标准差的点
> buyPoint<-function(ldata,x=2,dir=2){})     # ...代码省略

# 画交易信号点
> drawPoint<-function(ldata,pdata,titie,sDate,eDate,breaks="1 year"){
+     ldata<-na.omit(ldata)
+     g<-ggplot(aes(x=Index, y=Value),data=fortify(ldata[,1],melt=TRUE))
+     g<-g+geom_line()
+     g<-g+geom_line(aes(colour=Series),data=fortify(ldata[,-1],melt=TRUE))
+     
+     if(is.data.frame(pdata)){
+         g<-g+geom_point(aes(x=Index,y=Value,colour=op),data=pdata,size=4)
+     }else{
+         g<-g+geom_point(aes(x=Index,y=Value,colour=Series),data=na.omit(fortify(pdata,melt=TRUE)),size=4)  
+     }
+     g<-g+scale_x_date(labels=date_format("%Y-%m"),breaks=date_breaks(breaks),limits = c(sDate,eDate))
+     g<-g+xlab("") + ylab("Price")+ggtitle(title)
+     g
+ }
 
> buydata<-buyPoint(ldata20,2,2)                                       # 多空信号点
> drawPoint(ldata20[,c(1,2)],buydata$Value,title,sDate,eDate,'1 month')  # 画图

06

图中蓝色的点就是买入的信号点,由于股票我们只能进行单向交易,即低买高卖,并不能直接做空,所以我们要过滤股价高于移动平均线的点,只留下股价低于移动平均线的点,就是我们的买入信号点。

画出买入信号点,只保留股价低于移动平均线的点。


> buydata<-buyPoint(ldata20,2,1)        # 做多信号点
> drawPoint(ldata20[,c(1,2)],buydata$Value,title,sDate,eDate,'1 month') # 画图

07

计算卖出的信号点,当买入后,下一个股价与移动平均线的交点就是卖出的信号点,我们看一下是否可以赚到钱?!


# 计算卖出的信号点
> sellPoint<-function(ldata,buydata){})     # ...代码省略
> selldata<-sellPoint(ldata20,buydata)

# 买出信号
> selldata
           Value  ma20   dif        sd  rate
2015-07-10 14.86 14.61 -0.25 0.7384824 -0.34

我们把买入信号和卖出信号,合并到一张图上显示,如图所示。


> bsdata<-merge(buydata$Value,selldata$Value)
> names(bsdata)<-c("buy","sell")
> drawPoint(ldata20[,c(1,2)],bsdata,title,sDate,eDate,'1 month') #画图

08

从图上看,我们在绿色点位置进行买入,而在蓝色点位置进行卖出,确实是赚钱的。那么究竟赚了多少钱呢?我们还需要精确的计算出来。


# 合并交易信号
> signal<-function(buy, sell){})    # ...代码省略

# 交易信号数据
> sdata<-signal(buydata,selldata)                                   
> sdata
           Value    ma20     dif        sd  rate op
2015-06-19 14.63 16.0965  1.4665 0.6620157  2.22  B
2015-06-26 13.77 15.7720  2.0020 0.8271793  2.42  B
2015-06-29 13.56 15.6840  2.1240 0.9271735  2.29  B
2015-07-03 13.07 15.2545  2.1845 1.0434926  2.09  B
2015-07-10 14.86 14.6100 -0.2500 0.7384824 -0.34  S

利用交易信号数据,进行模拟交易。我们设定交易参数和规则:

  • 以10万元人民币为本金
  • 买入信号出现时,以收盘价买入,每次买入价值1万元的股票。如果连续出现买入信号,则一直买入。若现金不足1万元时,则跳过买入信号。
  • 卖出信号出现时,以收盘价卖出,一次性平仓信号对应的股票。
  • 手续费为0元

# 模拟交易
> trade<-function(sdata,capital=100000,fixMoney=10000){})    # ...代码省略

# 交易结果
> result<-trade(sdata,100000,10000)  

来看一下,每笔交易的明细。


> result$ticks
           Value    ma20     dif        sd  rate op      cash amount     asset     diff
2015-06-19 14.63 16.0965  1.4665 0.6620157  2.22  B  90007.71    683 100000.00     0.00
2015-06-26 13.77 15.7720  2.0020 0.8271793  2.42  B  80010.69   1409  99412.62  -587.38
2015-06-29 13.56 15.6840  2.1240 0.9271735  2.29  B  70016.97   2146  99116.73  -295.89
2015-07-03 13.07 15.2545  2.1845 1.0434926  2.09  B  60018.42   2911  98065.19 -1051.54
2015-07-10 14.86 14.6100 -0.2500 0.7384824 -0.34  S 103275.88      0 103275.88  5210.69

一共发生了5笔交易,其中4笔买入,1笔卖出。最后,资金剩余103275.88元,赚了3275.88元,收益率3.275%。

在卖出时,赚钱的交易有1笔。


> result$rise
           Value  ma20   dif        sd  rate op     cash amount    asset    diff
2015-07-10 14.86 14.61 -0.25 0.7384824 -0.34  S 103275.9      0 103275.9 5210.69

在卖出时,赔钱的交易,没有发生。


> result$fall
 [1] Value  ma20   dif    sd     rate   op     cash   amount asset  diff  
<0 行> (或0-长度的row.names)

接下来,我们再对比一下,资产净值和股价。


# 资产净值曲线
> drawAsset<-function(ldata,adata,sDate=FALSE,capital=100000){
+     if(!sDate) sDate<-index(ldata)[1]
+     adata<-rbind(adata,as.xts(capital,as.Date(sDate)))
+     
+     g<-ggplot(aes(x=Index, y=Value),data=fortify(ldata[,1],melt=TRUE))
+     g<-g+geom_line()
+     g<-g+geom_line(aes(x=as.Date(Index), y=Value,colour=Series),data=fortify(adata,melt=TRUE))
+     g<-g+facet_grid(Series ~ .,scales = "free_y")
+     g<-g+scale_y_continuous(labels=dollar_format(prefix = "¥"))
+     g<-g+scale_x_date(labels=date_format("%Y-%m"),breaks=date_breaks("2 months"),limits = c(sDate,eDate))
+     g<-g+xlab("") + ylab("Price")+ggtitle(title)
+     g
+ }

> drawAsset(ldata20,as.xts(result$ticks['asset']))  # 资产净值曲线

09

刚才我们是对一支股票进行了测试,发现是有机会的,那么我再换另外一支股票,看一下是否用同样的效果呢?我们把刚才数据操作的过程,封装到统一的quick函数,就可以快速验证均值回归在其他股票的表现情况了。


> quick<-function(title,sDate,eDate){}  # ...代码省略

我们用乐视网(300104)试一下,看看有没有赚钱的机会!!


> title<-"300104.SZ"
> sDate<-as.Date("2015-01-01") #开始日期
> eDate<-as.Date("2015-07-10") #结束日期

> quick(title,sDate,eDate)
$ticks
           Value    ma20     dif       sd  rate op      cash amount     asset     diff
2015-06-19 55.04 69.9095 14.8695 5.347756  2.78  B  90037.76    181 100000.00     0.00
2015-06-23 54.30 68.8075 14.5075 5.477894  2.65  B  80046.56    365  99866.06  -133.94
2015-06-24 56.21 67.8735 11.6635 5.404922  2.16  B  70097.39    542 100563.21   697.15
2015-06-25 51.80 66.8775 15.0775 5.770806  2.61  B  60099.99    735  98172.99 -2390.22
2015-06-26 46.79 65.9830 19.1930 6.580622  2.92  B  50133.72    948  94490.64 -3682.35
2015-06-29 47.05 64.9445 17.8945 7.096230  2.52  B  40159.12   1160  94737.12   246.48
2015-07-07 47.86 58.8150 10.9550 5.401247  2.03  B  30204.24   1368  95676.72   939.60
2015-07-10 57.92 57.3520 -0.5680 5.625309 -0.10  S 109438.80      0 109438.80 13762.08

$rise
           Value   ma20    dif       sd rate op     cash amount    asset     diff
2015-07-10 57.92 57.352 -0.568 5.625309 -0.1  S 109438.8      0 109438.8 13762.08

$fall
 [1] Value  ma20   dif    sd     rate   op     cash   amount asset  diff  
<0 行> (或0-长度的row.names)

从数据结果看,我们又赚到了。一共发生了8笔交易,其中7笔买入,1笔卖出。最后,资金剩余109438.80元,赚了9438.80元,收益率9.43%。

画出交易信号图


> title<-"300104.SZ"
> sDate<-as.Date("2015-01-01") #开始日期
> eDate<-as.Date("2015-07-10") #结束日期

> stock<-data[[title]]
> cdata<-stock[dateArea(sDate,eDate,360)]$Close
> ldata<-ma(cdata,c(20))
> ldata<-getMaSd(ldata,20,sDate,eDate)
> buydata<-buyPoint(ldata,2,1)  
> selldata<-sellPoint(ldata,buydata)
> bsdata<-merge(buydata$Value,selldata$Value)
> drawPoint(ldata[,c(1,2)],bsdata,title,sDate,eDate,'1 month') #画图

10

在恐慌的6月份,当别人都被套牢30%以上的情况下,我们还朿9%正收益,那么应该是多么舒心的一件事情啊!!

3. 量化选股

上文中,我们用2支股票进行了测试,发现均值回归模型是适合于股票交易的。如果我们利用模型对全市场的股票进行扫描,应用会产生更多的交易信号,找到更多的投资机会,这样我们就能如何能获得更大的收益。

那么,接下来我们就根据均值回归的理论进行量化选股。

根据我们之前的经验,当股价与平均标准差的偏离越大,有可能带来的收益就越大。那么通过量化的手段,在整个的市场2700多支股票中,把每天偏离最大股票的找出来进行交易,就可以有效地分配我们的资金,进行更有效的投资。我们要试一下,市场是否是和我们的思路是一致的。

对全市场股票进行扫描,首先计算差值、平均值和平均标准差。


> sDate<-as.Date("2015-01-01")                # 开始日期
> eDate<-as.Date("2015-07-10")                # 结束日期

# 计算差值、平均值和平均标准差
> data0<-lapply(data,function(stock){})       # 代码省略

# 去掉空数据
> data0<-data0[!sapply(data0, is.null)]      

# 全市场股票
> length(data)
[1] 2782

# 有效的股票
> length(data0)
[1] 2697

# 查看第1支股票
> head(data0[[1]])
              Value     ma20         dif        sd  rate
2015-01-05 13.23673 12.18613 -1.05059293 0.6556366 -1.60
2015-01-06 13.03842 12.23778 -0.80064848 0.6021093 -1.33
2015-01-07 12.79055 12.24810 -0.54244141 0.4754686 -1.14
2015-01-08 12.36089 12.29975 -0.06114343 0.5130410 -0.12
2015-01-09 12.46004 12.33651 -0.12352626 0.5150453 -0.24
2015-01-12 12.20390 12.37163  0.16773131 0.5531618  0.30

第一次扫描后,有2697支股票是符合条件的,有85支股票由于数据样本不足被排除。

接下来,继续对2697支股票进行筛选,找到符合要求的买入信号点。


# 计算买入信号
> buys<-lapply(data0,function(stock){})  # ...代码省略 

# 去掉空数据
> buys<-buys[!sapply(buys, is.null)] 

# 查看有买入信号的股票
> length(buys)
[1] 1819

# 查看买入信号
> head(buys)
$`000001.SZ`
           Value    ma20    dif        sd rate
2015-06-19 14.63 16.0965 1.4665 0.6620157 2.22
2015-06-26 13.77 15.7720 2.0020 0.8271793 2.42
2015-06-29 13.56 15.6840 2.1240 0.9271735 2.29
2015-07-03 13.07 15.2545 2.1845 1.0434926 2.09

$`000002.SZ`
           Value   ma20   dif        sd rate
2015-03-05 11.90 12.568 0.668 0.2644101 2.53
2015-03-06 11.94 12.509 0.569 0.2674732 2.13

$`000004.SZ`
           Value    ma20     dif        sd rate
2015-01-05 15.69 17.7210  2.0310 0.7395717 2.75
2015-07-06 26.03 39.1540 13.1240 6.3898795 2.05
2015-07-07 23.43 38.2025 14.7725 6.9421723 2.13
2015-07-08 22.22 37.2635 15.0435 7.4287088 2.03

$`000005.SZ`
           Value    ma20    dif       sd rate
2015-07-06  6.02 10.9600 4.9400 2.381665 2.07
2015-07-07  5.42 10.5655 5.1455 2.333008 2.21

$`000006.SZ`
              Value     ma20       dif      sd rate
2015-01-19 5.829283 6.519462 0.6901792 0.26929 2.56

$`000007.SZ`
           Value    ma20    dif        sd rate
2015-02-06 12.47 14.4200 1.9500 0.6182860 3.15
2015-02-09 12.52 14.3270 1.8070 0.7440473 2.43
2015-02-10 12.10 14.1845 2.0845 0.8484250 2.46

通过计算发现,有1819支股票,在这半年中产生过买入信号。每支股票产生的买入信号的时间和频率都是不同,这样我们就可以把钱分散投资到不同的股票上,同时分散风险。如果交易信号同一天出现在多支的股票上,而我们资金有限,又想让收益最大化,那么我们可以选择偏离值最大的股票进行交易。

接下来,我们用程序找到每日偏离最大的股票。


# 合并数据,从list转型到data.frame
buydf<-ldply(buys,function(e){})    # ...代码省略

# 选出同一日rate最大的股票,做为买入信号
buydatas<-ddply(buydf, .(date), function(row){}) # ...代码省略

# 查看买入信号
> nrow(buydatas)
[1] 81

# 查看买入信号细节
> head(buydatas)
         .id       date      Value       ma20        dif         sd rate
1  002551.SZ 2015-01-05  16.573846  19.565446  2.9916000 0.74591596 4.01
2  002450.SZ 2015-01-06  18.548809  19.766636  1.2178275 0.34008453 3.58
3  300143.SZ 2015-01-07  11.480000  12.603000  1.1230000 0.32028018 3.51
4  300335.SZ 2015-01-08  12.113677  13.139601  1.0259238 0.21760484 4.71
5  300335.SZ 2015-01-09  12.243288  13.043888  0.8005994 0.22940845 3.49
6  300335.SZ 2015-01-12  11.994036  12.941694  0.9476584 0.23168313 4.09

最后,我们选出81个买入信号点,基本上每个交易日都是买入信号。有了买入信号,继续找到卖出信号。


# 卖出信号
> selldatas<-data.frame()     # ...代码省略

# 卖出信号去重
> selldatas<-unique(selldatas)  
> nrow(selldatas)
[1] 33

# 查看买出信号
> head(selldatas)
                Value      ma20         dif        sd  rate       .id       date op
2015-01-12  19.232308 18.848908 -0.38340000 0.9051374 -0.42 002551.SZ 2015-01-12  S
2015-01-08  19.814257 19.729006 -0.08525126 0.3782955 -0.23 002450.SZ 2015-01-08  S
2015-01-28  11.210000 11.019500 -0.19050000 0.7781848 -0.24 300143.SZ 2015-01-28  S
2015-01-21  13.190448 12.899321 -0.29112706 0.3871871 -0.75 300335.SZ 2015-01-21  S
2015-01-213  7.140000  6.989500 -0.15050000 0.2007652 -0.75 002505.SZ 2015-01-21  S
2015-01-22   5.561561  5.490668 -0.07089242 0.2127939 -0.33 600077.SH 2015-01-22  S

通过计算,一共有33个买出信号点。最后,合并买入信号和卖出信号,并计算收益。


> buydatas$op<-'B'                              # 买入标志
> selldatas$op<-'S'                             # 卖出标志
> sdatas<-rbind(buydatas,selldatas)             # 合并数据
> row.names(sdatas)<-1:nrow(sdatas)             # 重设行号
> sdatas<-sdatas[order(sdatas$.id),]            # 按股票代码排序

# 查看合并的信号
> head(sdatas)
          .id       date Value     ma20       dif         sd  rate op
36  000002.SZ 2015-03-05 11.90 12.56800  0.668000 0.26441011  2.53  B
100 000002.SZ 2015-03-16 12.49 12.38050 -0.109500 0.23702768 -0.46  S
58  000553.SZ 2015-05-06 14.35 15.50882  1.158824 0.38429912  3.02  B
110 000553.SZ 2015-05-21 16.57 15.18903 -1.380972 0.55647152 -2.48  S
26  000725.SZ 2015-02-09  2.80  3.11400  0.314000 0.07934585  3.96  B
94  000725.SZ 2015-02-16  3.09  3.06500 -0.025000 0.08182388 -0.31  S

最后,按照股票进行分组,分别计算个股的收益。


# 计算个股的收益
> slist<-split(sdatas[-1],sdatas$.id)      # 按股票代码分组
> results<-lapply(slist,trade)

# 查看信号的股票
> names(results)
 [1] "000002.SZ" "000553.SZ" "000725.SZ" "000786.SZ" "000826.SZ" "002240.SZ" "002450.SZ"
 [8] "002496.SZ" "002505.SZ" "002544.SZ" "002551.SZ" "002646.SZ" "002652.SZ" "300143.SZ"
[15] "300335.SZ" "300359.SZ" "300380.SZ" "300397.SZ" "300439.SZ" "300440.SZ" "300444.SZ"
[22] "600030.SH" "600038.SH" "600077.SH" "600168.SH" "600199.SH" "600213.SH" "600375.SH"
[29] "600490.SH" "600536.SH" "600656.SH" "600733.SH" "600890.SH" "601179.SH" "601186.SH"
[36] "601628.SH" "601633.SH" "601939.SH" "603019.SH"

我们查看万科A(000002)的股票。


> results[['000002.SZ']]$ticks
          date Value    ma20     dif        sd  rate op     cash amount    asset  diff
36  2015-03-05 11.90 12.5680  0.6680 0.2644101  2.53  B  90004.0    840 100000.0   0.0
100 2015-03-16 12.49 12.3805 -0.1095 0.2370277 -0.46  S 100495.6      0 100495.6 495.6

通过优化的规则设计,一共有2笔交易,赚了495元。如要我们没有进行算法优化,一直交易万科A,那么会发生3笔交易,我们可以赚955.95元。


> quick('000002.SZ',sDate,eDate)$ticks
           Value    ma20     dif        sd  rate op      cash amount    asset   diff
2015-03-05 11.90 12.5680  0.6680 0.2644101  2.53  B  90004.00    840 100000.0   0.00
2015-03-06 11.94 12.5090  0.5690 0.2674732  2.13  B  80010.22   1677 100033.6  33.60
2015-03-16 12.49 12.3805 -0.1095 0.2370277 -0.46  S 100955.95      0 100955.9 922.35

本文到此就要结束了!但其实还有很多的事情要做,比如对模型参数的优化,用10日均线代替20日均线,用3倍标准差偏移代替2倍标准差偏移,对样本进行正态分布的检验,结合其他趋势类模型共同产生信号等,这些就不是一篇文章可以解决的事情了。大家可以况客金融平台的网站上,发现更多不一样的策略。

本文从均值回归的理论的介绍开始,到市场特征检验,再到数学公式,R语言建模,历史数据回测,最后找到投资机会,是一套完整的从理论到实践的学习方法。虽然困难重重,但做为有理想的极客,我们是有能力来克服这些困难的。

本文同时用到了计算机、金融、数学、统计等多学科知识的结合,我认为这是技术复合人才未来的发展方向。如果说过去10年是房地产的黄金10年,那么未来的10年将是金融的黄金10年。当我们IT人掌握了足够的金融知识,一定会有能力去金融市场抢钱的。

抓住机会!!程序员,加油!

######################################################
看文字不过瘾,作者视频讲解,请访问网站:http://onbook.me/video
######################################################

转载请注明出处:
http://blog.fens.me/finance-mean-reversion/

打赏作者