MACD策略

前言

各位看官好,我是小陈。前两篇文章,我们一起写了个数据表格和走势图,恩,是不是很容易啊,有没有信心满满现在!那么,接下来,我们来个有点难度的。咱们一起写一下MACD股票策略吧。

移动平均线(MA)是股市中最常用的一种技术分析方法,用来在大行情的波动段找到有效的交易信号。移动平均线不仅简单,而且有效,对股市操作具有神奇的指导作用,能有效地打败大部分的主观策略,是炒股、炒期货的必备基本工具。

接下来,我们仔细研究一下MACD策略的简单而又神奇之处吧。

目录

1.MACD的介绍
2.均线模型展示
3.R语言实现MACD策略

1、MACD的介绍

在介绍MACD之前,我们来介绍一下MA,也就是移动平均线。
移动平均线(MA,Moving average)是以道·琼斯的”平均成本概念”为理论基础,采用统计学中”移动平均”的原理,将一段时期内的股票价格平均值连成曲线,用来显示股价的历史波动情况,进而反映股价指数未来发展趋势的技术分析方法。它是道氏理论的形象化表述。
移动平均线的计算方法就是求连续若干天的收盘价的算术平均。天数就是MA的参数。

计算公式: MA = (C1+C2+C3+C4+C5+….+Cn)/n ,C为收盘价,n为移动平均周期数。例如,5日移动平均价格计算方法为:

1
MA5 = (前四天收盘价+前三天收盘价+前天收盘价+昨天收盘价+今天收盘价)/5 

移动平均线依时间长短可分为三种,即短期移动平均线,中期移动平均线,长期移动平均线。短期移动平均线一般以5日或10日为计算期间,中期移动平均线大多以30日、60日为计算期间;长期移动平均线大多以100天和200天为计算期间。有根据处理数据的方式的不同,移动平均线又分为简单移动平均线(SMA)、加权移动平均线(WMA),以及指数平滑移动平均线(EMA)。

而今天我们说的,MACD(Moving Average Convergence / Divergence)也是基于移动平均线发展而来。

MACD称为指数平滑移动平均线,是从双指数移动平均线发展而来的,由快的指数移动平均线(EMA)减去慢的指数移动平均线,MACD的意义和双移动平均线基本相同,但阅读起来更方便。当MACD从负数转向正数,是买的信号。当MACD从正数转向负数,是卖的信号。当MACD以大角度变化,表示快的移动平均线和慢的移动平均线的差距非常迅速的拉开,代表了一个市场大趋势的转变。

2、均线模型展示

这里,为了让大家能够跟更好的理解,我特地制作一下走势图。
yieldchart

这是以IBM2010年1月到2012年1月的股价走势。其中黑线代表的是K线(该公司每天的股价),蓝线代表的是ma5(该公司每5天收盘价的均值连成的曲线),以及绿线ma20(该公司每20天收盘价的均值连成的曲线)。可以看到,ma20被无数个红点以及紫点覆盖掉了。红点是表示此时ma5>ma20,紫点是ma5<ma20.

从图中,我们可以直观的看到,红线总是在上升,而紫线则总是在下降。自然而然地,大家就会想到,我们应该在红线的第一个点买入,在紫线的第一个点卖出,这样,我们就可以挣得更多钱了。

当然也不是说,使用了MACD策略,我们就一定赚钱,坐着收钱了。毕竟股市可不是这样简单的东西。但是MACD策略却可以让我们更加清晰的看到市场的本质,提高了胜率。不过,即便是有输有赢,我们也要找到胜率最高的那一只股票是不。以下这幅图便是我们今天的任务了。使用我们今天学到的MACD策略,并对这么一组股票进行模拟交易,然后按照收益率高低进行排序。第一行是沪深300指数,以作比较。

3、R语言实现MACD策略

1)初始化

1
2
3
4
5
6
7
8
library(quantmod)
library(plyr)
library(TTR)
library(scales)
library(ggplot2)
library(qutke)
key <- 'faca4c8ff4dc1502d87944ea9cfd74f19918f0aef37fde6746b61d4d339bfcf3'
init(key)

2)选定参数

1
2
3
4
5
6
sDate<-as.Date("2015-1-1") #开始日期
eDate<-Sys.Date() #结束日期
tradingDay <- getDate(data='tradingDay',startdate=sDate,enddate=eDate,key=key)
length <- length(tradingDay)
sDate <- tradingDay[1]
eDate <- tradingDay[length]

可以通过getIndudtry方法获取况客的股票代码。不过因为有点多,所以我这里只选其中的20多支股票的股票代码。

1
2
3
4
5
# industry <- getIndustry(data='industryType',date=eDate,key=key)
# qtid <- industry$qtid
qtid <- c('002230.SZ','002715.SZ',"603789.SH", "603799.SH", "603806.SH", "603808.SH", "603818.SH","603828.SH", "603869.SH", "603883.SH",
"603885.SH", "603889.SH", "603898.SH", "603899.SH", "603901.SH", "603918.SH", "603939.SH", "603968.SH",
"603969.SH", "603988.SH" ,"603989.SH", "603993.SH", "603997.SH", "603998.SH")

3)获取每支股票的数据,包括每个交易日的收盘价、ma5、ma20.

1
2
3
4
dailyQuote <- getDailyQuote(data='mktFwdDaily',qtid=qtid,startdate=sDate,enddate=eDate,key=key)
ldata <- dailyQuote[,grep("qtid|date|close|ma5|ma20", names(dailyQuote)) ]
#为true时候,补全ldata中是na的数值,以最近日期的数据 进行赋值
ldata<-na.locf(ldata, fromLast=TRUE)

因为这次涉及到的数据处理比较多,听起来也比较虚,所以,我们在写代码的时候,可以多查看一下所得到的变量的数据结构是什么样子的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
> head(dailyQuote)
qtid date close volume value fwdAdj fwdAdjOpen fwdAdjHi fwdAdjLo fwdAdjClose ret logRet
1 000001.SZ 2015-01-05 16.02 286043643 4565387846 0.7574074 12.11094 12.33059 11.81556 12.13367 0.011364 0.011300
2 000001.SZ 2015-01-06 15.78 216642140 3453446168 0.7574074 12.00491 12.41391 11.77769 11.95189 -0.014981 -0.015095
3 000001.SZ 2015-01-07 15.48 170012067 2634796409 0.7574074 11.78526 11.98976 11.58833 11.72467 -0.019011 -0.019194
4 000001.SZ 2015-01-08 14.96 140771421 2128003432 0.7574074 11.73981 11.79283 11.28537 11.33081 -0.033592 -0.034169
5 000001.SZ 2015-01-09 15.08 250850023 3835378100 0.7574074 11.28537 12.02006 11.14146 11.42170 0.008021 0.007989
6 000001.SZ 2015-01-12 14.77 155329086 2293104602 0.7574074 11.26265 11.39898 10.98241 11.18691 -0.020557 -0.020771
vwap vol30 ma5 ma10 ma20 ma60
1 15.9605 8427448605 11.72 11.45 11.17 9.23
2 15.9408 8529417547 11.82 11.51 11.22 9.30
3 15.4977 8476692763 11.91 11.52 11.23 9.37
4 15.1167 8460683917 11.83 11.54 11.27 9.43
5 15.2895 8493042075 11.71 11.62 11.31 9.49
6 14.7629 8437985489 11.52 11.62 11.34 9.55

> head(ldata)
qtid date close ma5 ma20
1 002230.SZ 2015-01-05 27.40 17.11 18.17
2 002230.SZ 2015-01-06 27.72 16.99 18.12
3 002230.SZ 2015-01-07 27.20 16.95 18.08
4 002230.SZ 2015-01-08 27.20 17.07 18.04
5 002230.SZ 2015-01-09 27.10 17.13 17.97
6 002230.SZ 2015-01-12 27.91 17.19 17.93

4)以UP、Down标定。

对每只股票,以其每个交易日的ma20与ma5进行比较,当ma5>ma20时候,取UP,当ma5<ma20时候,取Down。然后,对数据以qtid为准进行分离并依照日期排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 散点数据
genPoint<-function(arg=c(),ldata){
if(length(arg)>2){
stop('The length of args is two.')
}
if(length(arg)<2){
arg <- c(arg,2)
}
#[,c(1,arg[1])]
pdata <- ldata[which(ldata[,arg[2]]>ldata[,arg[1]]),]
pdata <- cbind(pdata,'op'=c('Down')) #添加op列
xdata <- ldata[which(ldata[,arg[2]]<=ldata[,arg[1]]),]
xdata <- cbind(xdata,'op'='UP')
return(rbind(pdata,xdata))
}
#以qtid分离数据,并返回一个list
separateQtid<-function(pdata,qtid){
if(length(qtid)<=0){
stop('The length of qtid is at least one.')
}
xdata <- list()
for(id in qtid){
xdata[[id]] <- pdata[which(pdata$qtid==id),]
}
return(xdata)
}
pdata<-genPoint(c(5,4),ldata)
pdata <- separateQtid(pdata,qtid)
#以date数据进行排序
for(id in qtid){
pdata[[id]] <- pdata[[id]][order(pdata[[id]]$date,decreasing=F),]
}

这个时候pdata是一个list类型的结构,所以我只列出其中一个元素的前几行数据以展示。

1
2
3
4
5
6
7
8
> head(pdata[[1]])
qtid date close ma5 ma20 op
1 002230.SZ 2014-01-02 48.07 17.36 17.28 Down
2 002230.SZ 2014-01-03 47.05 17.41 17.29 Down
3 002230.SZ 2014-01-06 45.12 17.27 17.29 UP
4 002230.SZ 2014-01-07 45.08 17.12 17.27 UP
5 002230.SZ 2014-01-08 45.44 16.95 17.24 UP
6 002230.SZ 2014-01-09 45.42 16.75 17.18 UP

5)选择交易点。

也就是上面的走势图所说的,在红线的第一点买入,在紫线的第一点卖出。
模型设计思路:

  1. 以5日均线和20日均线的交叉,进行交易信号的判断。
  2. 当5日均线上穿20日均线则买入(红色),下穿20日均线卖出(蓝色)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Signal<-function(ldata=c(),pdata,qtid){
if(length(qtid)<=0){
stop('The length of qtid is at least one.')
}
op1 <- list()
for(id in qtid){
pdata[[id]] <- pdata[[id]][order(pdata[[id]]$date,decreasing=F),]
op <- pdata[[id]][1,]
tmp <- op$op
i <- 2
nrow <- nrow(pdata[[id]])
while(i<=nrow){
if(tmp!=pdata[[id]][i,]$op){
op <- rbind(op,pdata[[id]][i,])
}
tmp <- pdata[[id]][i,]$op
i=i+1
}
op1[[id]] <- op
}
return(op1)
}
tdata<-Signal(ldata,pdata,qtid)

同pdata一样,这也是只展示了tdata的部分。

1
2
3
4
5
6
7
8
> head(tdata[[1]])
qtid date close ma5 ma20 op
1 002230.SZ 2014-01-02 48.07 17.36 17.28 Down
3 002230.SZ 2014-01-06 45.12 17.27 17.29 UP
10 002230.SZ 2014-01-15 48.80 17.14 17.10 Down
36 002230.SZ 2014-02-27 49.85 19.18 19.48 UP
64 002230.SZ 2014-04-09 46.82 16.94 16.86 Down
72 002230.SZ 2014-04-21 25.71 16.88 16.91 UP

6)模拟交易

预先设定参数是:本金为100000元,每次都全部买入卖出,即持仓比例为1,手续费为0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#利用交易信号数据,进行模拟交易
#参数:交易信号tdata ,本金capital,持仓比例position,手续费比例fee
trade<-function(tdata,capital=100000,position=1,fee=0){
value <- as.numeric(tdata$close)
asset <- capital
if(tdata[1,]$op=='Down'){
amount <- (asset*position)%/%value[1]
cash <- (asset*position)%%value[1]
#奇数为0,偶数为asset减去上一次的。
diff <- 0.00
}else if(tdata[1,]$op=='UP'){
amount <- 0
cash <- asset
diff <- 0.00
}
i<-2
nrow <- nrow(tdata)
while(i<=nrow){
#Down 买入
if(tdata[i,]$op=='Down'){
asset <- c(asset,tail(asset,1))
amount <- c(amount,(tail(asset,1)*position)%/%value[i])
cash <- c(cash,(tail(asset,1)*position)%%value[i]+(tail(asset,1)*(1-position)))
diff <- c(diff,0.00)
}else if(tdata[i,]$op=='UP'){
#UP 卖出
cash <- c(cash,(tail(amount,1)*value[i])+tail(cash,1))
amount <- c(amount,0)
asset <- c(asset,tail(amount,1)*value[i]+tail(cash,1))
diff <- c(diff,tail(asset,1)-tail(asset,2)[1])
}
i=i+1
}
result <- list()
result[['ticks']] <- cbind(tdata,cash,amount,asset,diff)
indexRise <- c()
indexFall <- c()
i <- 2
while(i<=nrow){
if(result[['ticks']][i,]$diff > 0){
indexRise <- c(indexRise,i-1,i)
}else if(result[['ticks']][i,]$diff < 0){
indexFall <- c(indexFall,i-1,i)
}
i <- i+1
}
result[['rise']] <- result$ticks[indexRise,]
result[['fall']] <- result$ticks[indexFall,]
return(result)
}
#设定交易参数,以$10W为本金,满仓买入或卖出,手续为0,传入交易信号。
# 查看每笔交易,将会变成一个list,保存每一个qtid的交易记录result
#每一个qtid的交易记录
tradeList <- list()
for(id in qtid){
tradeList[[id]] <- trade(tdata[[id]],100000)
}

7)计算收益率,并记录在一个列表里面,生成dataframe,信息包含日期,代码,名称,收益率,涨跌幅,近1周涨跌,近1月涨跌,年初至今涨跌。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
income <- c()
quoteChangeDaily <- c()
quoteChangeWeek <- c()
quoteChangeMonth <- c()
quoteChangeFYear <- c()
for(id in qtid){
#C(),收益率。收益率 = (当天的股价-买入价)/ 买入价 *100%
income <- c(income,(tail(tradeList[[id]]$ticks$asset,1)-tradeList[[id]]$ticks$asset[1])/tradeList[[id]]$ticks$asset[1]*100)
mktYest <- getDailyQuote(data='mktFwdDaily',qtid = id,startdate=pdata[[id]]$date[nrow(pdata[[id]])-1],enddate=pdata[[id]]$date[nrow(pdata[[id]])-1],key=key)
mktWeek <- getDailyQuote(data='mktFwdDaily',qtid = id,startdate=pdata[[id]]$date[nrow(pdata[[id]])-5],enddate=pdata[[id]]$date[nrow(pdata[[id]])-5],key=key)
mktMonth <- getDailyQuote(data='mktFwdDaily',qtid = id,startdate=pdata[[id]]$date[nrow(pdata[[id]])-20],enddate=pdata[[id]]$date[nrow(pdata[[id]])-20],key=key)
mktFYear <- getDailyQuote(data='mktFwdDaily',qtid = id,startdate=pdata[[id]]$date[1],enddate=pdata[[id]]$date[1],key=key)
#涨跌幅 .涨跌幅的计算公式是:(当前最新成交价(或收盘价)-开盘参考价)÷开盘参考价×100% [which(mktYest$qtid==id),]
quoteChangeDaily <- c(quoteChangeDaily,(as.numeric(tail(pdata[[id]], 1)$close)-mktYest$close)/mktYest$close*100)
#近1周涨跌
quoteChangeWeek <- c(quoteChangeWeek,(as.numeric(tail(pdata[[id]], 1)$close)-mktWeek$close)/mktWeek$close*100)
#近1月涨跌
quoteChangeMonth <- c(quoteChangeMonth,(as.numeric(tail(pdata[[id]], 1)$close)-mktMonth$close)/mktMonth$close*100)
#年初至今涨跌
quoteChangeFYear <- c(quoteChangeFYear,(as.numeric(tail(pdata[[id]], 1)$close)-mktFYear$close)/mktFYear$close*100)
}
ChiAbbr <- getMD(data='keyMap',qtid=qtid,key=key)$ChiAbbr
maDF <- data.frame('1'=tail(dailyQuote,1)$date,'2'=qtid,'3'=ChiAbbr,
'4'=income,'5'=quoteChangeDaily,'6'=quoteChangeWeek,'7'=quoteChangeMonth,
'8'=quoteChangeFYear)
#排序
maDF <- maDF[order(maDF$X4,decreasing=T),]

8)求取沪深300指数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
hs300<-getDailyQuote(data='mktDataIndex',qtid=c('000300.SH'),startdate=sDate,enddate=eDate,key=key) 

id <- c('000300.SH')

hs300mktWeek <- hs300[nrow(hs300)-5,]

hs300mktMonth <- hs300[nrow(hs300)-20,]

hs300mktFYear <- hs300[1,]

quoteChangeDaily <- (tail(hs300,1)$close-tail(hs300,1)$prevClose)/tail(hs300,1)$prevClose*100

quoteChangeWeek <- (tail(hs300,1)$close-hs300mktWeek$close)/hs300mktWeek$close*100

quoteChangeMonth <- (tail(hs300,1)$close-hs300mktMonth$close)/hs300mktMonth$close*100

quoteChangeFYear <- (tail(hs300,1)$close-hs300mktFYear$close)/hs300mktFYear$close*100

hs300Income <- c()

hs300Income <- (tail(hs300,1)$close-hs300mktFYear$close)/hs300mktFYear$close*100

#生成dataframe
hs300Dt <- data.frame('1'=tail(dailyQuote,1)$date,'2'='000300.SH','3'='沪深300','4'=hs300Income,'5'=quoteChangeDaily,'6'=quoteChangeWeek,'7'=quoteChangeMonth,'8'=quoteChangeFYear)

沪深300指数的数据是这样的:

1
2
3
> hs300Dt 
X1 X2 X3 X4 X5 X6 X7 X8
1 2016-01-22 000300.SH 沪深300 -14.50151 1.042311 -0.1688989 -18.69586 -14.50151

9)合并数据,并发送到况客投研平台,进行后期的修改。

1
2
3
maDF <- rbind(hs300Dt,maDF)
names(maDF)<-c('日期','代码','名称','收益率(%)','涨跌幅(%)','周涨跌幅(%)','月涨跌幅(%)','年初至今涨跌幅(%)')
postData(maDF,name='maDataF',key=key)

这样,便算是完成了。大家如果有什么不明白的话,可以随时艾特我哦。

作者

yalechen

发布于

2022-07-24

更新于

2022-07-24

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.
You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.