張丹:均值回歸 發(fā)現(xiàn)逆市中的投資機(jī)會(huì)
原創(chuàng)前言
在股票市場中有兩種典型的投資策略:趨勢追蹤(Trend Following) 和 均值回歸(Mean Reversion)。 趨勢追蹤策略的特點(diǎn)在大行情的波動(dòng)段找到有效的交易信號,不僅簡單而且有效,我之前寫的一篇文章 兩條均線打天下 就屬于趨勢追蹤策略。而均值回歸策略則是一種反趨勢策略,一波大幅上漲后容易出現(xiàn)下跌,而一波大幅下跌后容易出現(xiàn)上漲。其特點(diǎn)在振蕩的在震蕩的市場中非常有效,捕捉小的機(jī)會(huì),本文就將介紹這種策略。
目錄
- 均值回歸原理
- 均值回歸模型和實(shí)現(xiàn)
- 量化選股
- 關(guān)于作者
1. 均值回歸原理
在金融學(xué)中,均值回歸是價(jià)格偏離均衡價(jià)格水平一定程度后向均衡價(jià)格靠攏的規(guī)律。本質(zhì)上,均值回歸就是哲學(xué)思想中所說的物極必反,可以簡單地概括為“漲多必跌,跌多必漲”的規(guī)律。
均值回歸是指股票價(jià)格無論高于或低于均值(均衡價(jià)格水平)都會(huì)以很高的概率向均值回歸的趨勢。根據(jù)這個(gè)理論,股票價(jià)格總是圍繞其均值上下波動(dòng)。一種上漲或者下跌的趨勢不管其延續(xù)的時(shí)間多長都不能永遠(yuǎn)持續(xù)下去,最終均值回歸的規(guī)律一定會(huì)出現(xiàn):漲得太多了,就會(huì)向均值移動(dòng)下跌;跌得太多了,就會(huì)向均值移動(dòng)上升。如果我們認(rèn)為事物總要回歸常態(tài),并且基于這樣的預(yù)期來做任何決策的時(shí)候,我們就是在應(yīng)用均值回歸的理論。
下面以平安銀行(000001)股票日K線圖為例,可以非常直觀的了解均值回歸這種現(xiàn)象, 截取2005年到2015年7月的股票數(shù)據(jù),股價(jià)為向前復(fù)權(quán)的價(jià)格。
上圖中有3條曲線,黑色線是平安銀行向前復(fù)權(quán)后的每日股價(jià),紅色線為20日均線,藍(lán)色線為60日均線。關(guān)于均線的介紹,請參考文章 兩條均線打天下。圖中還有一條紅色的水平線虛線,是這10年的股價(jià)平均值等于7.14元。這10年間,平安銀行的股價(jià)經(jīng)歷了幾波上漲和下跌,多次穿越7.14平均值。那么這個(gè)現(xiàn)象就是我們要討論的均值回歸。
1.1 均值回歸的3個(gè)特性
均值回歸是價(jià)值投資理論成立的一個(gè)核心理論,具有3個(gè)特性:必然性、不對稱性、政府調(diào)控。
必然性,股票價(jià)格不能總是上漲或下跌,一種趨勢不管其持續(xù)的時(shí)間多長都不能永遠(yuǎn)持續(xù)下去。在一個(gè)趨勢內(nèi),股票價(jià)格呈持續(xù)上升或下降,我們稱之為均值回避(Mean Aversion)。當(dāng)出現(xiàn)相反趨勢時(shí)就呈均值回歸(Mean Reversion),但回歸的周期有隨機(jī)性是我們不能預(yù)測。不同的股票市場,回歸的周期會(huì)不一樣的,就算是相同的市場,回歸的周期也是不一樣的。
我們換支股票,以蘇寧云商(002024)股票日K線圖為例, 同樣截取2005年到2015年7月的向前復(fù)權(quán)的股價(jià)數(shù)據(jù),如下圖所示。我們看到蘇寧云商在2006年到2007年有一波大漲隨后下跌;從2009到2010年時(shí),第二波大漲;2013年下半年迎來第三波大漲;2014年下半年到2015年第四波大漲。從圖形上可以直觀看到,2015年這波漲的最急,波動(dòng)率也是最大的;從現(xiàn)象中,我們可以判斷一種趨勢不管其持續(xù)的時(shí)間多長都不能永遠(yuǎn)持續(xù)下去。
不對稱性,股價(jià)波動(dòng)的幅度與速度是不一樣的,回歸時(shí)的幅度與速度具有隨機(jī)性。對稱的均值回歸才是不正常的、偶然的,這一點(diǎn)也也可以從股票中所驗(yàn)證。
我們合并平安銀行(000001)和蘇寧云商(002024)股票日K線圖為例,所下圖所示。兩支股票在2007年中,都趕上了大的上漲行情,曲線基本吻合。到2008年2支股票都遇到了大跌,但波動(dòng)率和速度都是不一樣的,隨后在2010年到2012年出現(xiàn)了完成不一樣的走勢,無規(guī)律可尋,體現(xiàn)了均值回歸時(shí)的隨機(jī)性和不對稱性。
政府行為,股票收益率不會(huì)偏離價(jià)值均值時(shí)間太久,市場的內(nèi)在力量會(huì)促使其向內(nèi)在價(jià)值回歸。市場在沒有政府政策的作用下,股票價(jià)格會(huì)在市場機(jī)制下自然地向均值回歸。但這并不否定政府行為對促進(jìn)市場有效性的作用,因?yàn)槭袌銎x內(nèi)在價(jià)值后并不等于立即就會(huì)向內(nèi)在價(jià)值回歸,很可能會(huì)出現(xiàn)持續(xù)地均值回避。政府行為會(huì)起到抑制市場調(diào)節(jié)市場的作用,是必不可少的因素之一,市場失靈也是政府參與調(diào)控的直接的結(jié)果。
對于政府政策行為,比如升準(zhǔn)、降準(zhǔn)、升息、降息,在股市中都會(huì)有比如明顯的體現(xiàn)。房地產(chǎn)股、銀行股,都會(huì)受到國家宏觀調(diào)控的直接的影響。下如所示,在圖中增加萬科A(0000002)的股票,圖中3條線分別是平安銀行,萬科A,蘇寧云商3支股票。我們發(fā)現(xiàn)地產(chǎn)和銀行的股價(jià)走勢是比較相近的,而電商的走勢是不太一樣的。
另外,增加2種顏色的輔助線,紅色為升息的時(shí)間點(diǎn)和利率變動(dòng)值,黃色為降息的時(shí)間點(diǎn)和利率變動(dòng)值。當(dāng)2007年股市超漲的時(shí)候,國家宏觀調(diào)控通過升息鼓勵(lì)存款,抑制高股價(jià);當(dāng)股票超跌的時(shí)候,通過降息推動(dòng)投資和消費(fèi)。2015年金融改革,政府一直都在降息拉動(dòng)股市。從圖中,我們看到萬科A和平安銀行對于升息和降息的調(diào)控是比較明顯的,對于蘇寧云商就不是特別的明顯了。
通過對市場的回顧,我們基本驗(yàn)證了均值回歸的理論是和市場的行為是一致的。那么,接下來我們應(yīng)該如何應(yīng)用這個(gè)理論來找到投資的切入點(diǎn)呢?
1.2 計(jì)算原理和公式
從價(jià)值投資的角度,我們發(fā)現(xiàn)股價(jià)會(huì)在平均值上下波動(dòng),但如果考慮到資金的時(shí)間成本,把錢都壓在股市中,等待幾年的大行情,也是很不劃算的。那么我們就需要對價(jià)值均值進(jìn)行重新定義,以20日均值來代替長期均值,找到短周期的一種投資方法。
計(jì)算原理:取日K線,以N日均線做為均值回歸的短期均衡價(jià)格水平(均值),計(jì)算股價(jià)到均值的差值,求出差值的N日的平均標(biāo)準(zhǔn)差,從而判斷差值的對于均值的偏離,當(dāng)偏離超過2倍標(biāo)準(zhǔn)差時(shí),我們就認(rèn)為股價(jià)超漲或超跌,股價(jià)會(huì)遵循均值回歸的理論,向均值不停地進(jìn)行修復(fù)。
計(jì)算公式:
- N日平均值 = [T日股價(jià) + (T-1)日股價(jià) + ... + (T-(N-1))日股價(jià)]/N
- 差值 = N日平均值 - N日股價(jià)
- N日平均標(biāo)準(zhǔn)差 = [T日差值 + (T-1)日差值 + ... + (T-(N-1))日差值]/N
如果N為20日,則
- 20日平均值 = [T日股價(jià) + (T-1)日股價(jià) + ... + (T-19)日股價(jià)]/20
計(jì)算偏離點(diǎn)
- 20日平均值 = [T日股價(jià) + (T-1)日股價(jià) + ... + (T-19)日股價(jià)]/20
我們以偏離點(diǎn)作為買入信號點(diǎn),以均線和股價(jià)的下一個(gè)交點(diǎn)做為賣出信號點(diǎn)。這樣我們就把均值回歸的投資理論,變成了一個(gè)數(shù)學(xué)模型。
#p#
2.均值回歸模型和實(shí)現(xiàn)
接下來,我們利用R語言對股票數(shù)據(jù)的進(jìn)行操作,來實(shí)現(xiàn)一個(gè)均值回歸模型的實(shí)例,從而驗(yàn)證我的們投資理論,是否能發(fā)現(xiàn)賺錢的機(jī)會(huì)。
2.1 數(shù)據(jù)準(zhǔn)備
R語言本身提供了豐富的金融函數(shù)工具包,時(shí)間序列包zoo和xts,指標(biāo)計(jì)算包TTR,數(shù)據(jù)處理包plyr,可視包ggplot2等,我們會(huì)一起使用這些工具包來完成建模、計(jì)算和可視化的工作。關(guān)于zoo包和xts包的詳細(xì)使用可以參考文章,R語言時(shí)間序列基礎(chǔ)庫zoo,可擴(kuò)展的時(shí)間序列xts。
我本次用到的數(shù)據(jù)是從 況客 直接導(dǎo)出的,況客 會(huì)提供各種類型的金融數(shù)據(jù)API,讓開發(fā)者可以免費(fèi)下載。當(dāng)然,你也可以用quantmod包從Yahoo財(cái)經(jīng)下載。
本文用到的數(shù)據(jù),包括A股日K線(向前復(fù)權(quán))數(shù)據(jù),從2014年7月到2015年日7月,以CSV格式保存到本地文件stock.csv。
數(shù)據(jù)格式如下:
- 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列,開盤價(jià),Open,8.14
- 第4列,最高價(jià),High,8.18
- 第5列,最低價(jià),Low,8.10
- 第6列,收盤價(jià),Close,8.17
- 第7列,交易量,Volume,28604171
通過R語言加載股票數(shù)據(jù),由于數(shù)據(jù)所有股票都是混合在一起的,而進(jìn)行計(jì)算時(shí)又需要按每支票股計(jì)算,所以在數(shù)據(jù)加載時(shí)我就進(jìn)行了轉(zhuǎn)換,按股票代碼進(jìn)行分組,生成R語言的list對象,同時(shí)把每支股票的data.frame類型對象轉(zhuǎn)成XTS時(shí)間序列類型對象,方便后續(xù)的數(shù)據(jù)處理。
- #加載工具包
- > library(plyr)
- > library(xts)
- > library(TTR)
- > library(ggplot2)
- > library(scales)
- # 讀取CSV數(shù)據(jù)文件
- > read<-function(file){
- + df<-read.table(file=file,header=FALSE,sep = ",", na.strings = "NULL") # 讀文件
- + names(df)<-c("code","date","Open","High","Low","Close","Volume") # 設(shè)置列名
- + dl<-split(df[-1],df$code) # 按ccode分組
- +
- + lapply(dl,function(row){ # 換成xts類型數(shù)據(jù)
- + xts(row[-1],order.by = as.Date(row$date))
- + })
- + }
- # 加載數(shù)據(jù)
- > data<-read("stock.csv")
- # 查看數(shù)據(jù)類型
- > class(data)
- [1] "list"
- # 查看數(shù)據(jù)的索引值
- > head(names(data))
- [1] "000001.SZ" "000002.SZ" "000004.SZ" "000005.SZ" "000006.SZ" "000007.SZ"
- # 查看包括的股票數(shù)量
- > 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
把數(shù)據(jù)準(zhǔn)備好了,我們就可以來建立模型了。
2.2 均值回歸模型
為了能拉近我們對市場的了解,我們?nèi)?015年1月1日開始的數(shù)據(jù),來創(chuàng)建均值回歸模型。以平安銀行(000001)的為例,畫出平安銀行的2015年以來的日K線和均線。
- # 獲得時(shí)間范圍
- > dateArea<-function(sDate=Sys.Date()-365,eDate= Sys.Date(),before=0){ #開始日期,結(jié)束日期,提單開始時(shí)
- + if(class(sDate)=='character') sDate=as.Date(sDate)
- + if(class(eDate)=='character') eDate=as.Date(eDate)
- + return(paste(sDate-before,eDate,sep="/"))
- + }
- # 計(jì)算移動(dòng)平均線
- > 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]] # 獲得股票數(shù)據(jù)
- > sDate<-as.Date("2015-01-01") # 開始日期
- > eDate<-as.Date("2015-07-10") # 結(jié)束日期
- > cdata<-SZ000011[dateArea(sDate,eDate,360)]$Close # 獲得收盤價(jià)
- > ldata<-ma(cdata,c(5,20,60)) # 選擇移動(dòng)平均指標(biāo)
- # 打印移動(dòng)平均指標(biāo)
- > 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
我們設(shè)置3條移動(dòng)平均線,分別是5日平均線,20日平均線,60日平均線,當(dāng)然也可以按照自己的個(gè)性要求設(shè)置符合自己的周期。畫出日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) # 畫圖
如圖所示,60日的移動(dòng)平均線是最平滑的,5日的移動(dòng)平均線是波動(dòng)最大的。5日平均線和股價(jià)的交叉,明顯多于60日平均線和股價(jià)的交叉。那么可以說在相同的時(shí)間周期內(nèi),短周期的移動(dòng)平均線,比長周期的移動(dòng)平均線更具有均值回歸的特點(diǎn)。
我們分別計(jì)算不同周期的,股價(jià)與移動(dòng)平均線的差值的平均標(biāo)準(zhǔn)差。
- > getMaSd<-function(ldata,mas=20,sDate,eDate){}) # ...代碼省略
- # 5日平均線的差值、平均標(biāo)準(zhǔn)差
- > 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日平均線的差值、平均標(biāo)準(zhǔn)差
- > 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日平均線的差值、平均標(biāo)準(zhǔn)差
- > 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日的平均線的差值和平均標(biāo)準(zhǔn)差是最小的,而60日的平均線的差值和平均標(biāo)準(zhǔn)差是最大的。如果我們以5日移動(dòng)平均線做為均值時(shí),會(huì)頻繁進(jìn)行交易,但每次收益都很小,可能都不夠手續(xù)費(fèi)的成本;另一方面,如果我們以60日移動(dòng)平均線做為均值時(shí),交易次數(shù)會(huì)較少,但可能會(huì)出現(xiàn)股票成形趨勢性上漲或下跌,長時(shí)間不能回歸的情況,可能會(huì)造成現(xiàn)金頭寸的緊張。綜合上面的2種情況,我們可以選擇20日均線作為均值的標(biāo)的。
根據(jù)模型的計(jì)算公式,當(dāng)差值超過2倍的平均標(biāo)準(zhǔn)差時(shí),我們認(rèn)為股價(jià)出現(xiàn)了偏離,以偏離點(diǎn)做為模型的買入信號,當(dāng)均線和股價(jià)再次相交時(shí)做為賣出信號。
上一步,我們已經(jīng)計(jì)算出了偏離值,并保存在rate列中。下面我們要找到大于2倍標(biāo)準(zhǔn)化差的點(diǎn),并畫圖。
- # 差值和平均標(biāo)準(zhǔn)差,大于2倍平均標(biāo)準(zhǔn)差的點(diǎn)
- > buyPoint<-function(ldata,x=2,dir=2){}) # ...代碼省略
- # 畫交易信號點(diǎn)
- > 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) # 多空信號點(diǎn)
- > drawPoint(ldata20[,c(1,2)],buydata$Value,title,sDate,eDate,'1 month') # 畫圖
圖中藍(lán)色的點(diǎn)就是買入的信號點(diǎn),由于股票我們只能進(jìn)行單向交易,即低買高賣,并不能直接做空,所以我們要過濾股價(jià)高于移動(dòng)平均線的點(diǎn),只留下股價(jià)低于移動(dòng)平均線的點(diǎn),就是我們的買入信號點(diǎn)。
#p#
畫出買入信號點(diǎn),只保留股價(jià)低于移動(dòng)平均線的點(diǎn)。
- > buydata<-buyPoint(ldata20,2,1) # 做多信號點(diǎn)
- > drawPoint(ldata20[,c(1,2)],buydata$Value,title,sDate,eDate,'1 month') # 畫圖
計(jì)算賣出的信號點(diǎn),當(dāng)買入后,下一個(gè)股價(jià)與移動(dòng)平均線的交點(diǎn)就是賣出的信號點(diǎn),我們看一下是否可以賺到錢?!
- # 計(jì)算賣出的信號點(diǎn)
- > 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') #畫圖
從圖上看,我們在綠色點(diǎn)位置進(jìn)行買入,而在藍(lán)色點(diǎn)位置進(jìn)行賣出,確實(shí)是賺錢的。那么究竟賺了多少錢呢?我們還需要精確的計(jì)算出來。
- # 合并交易信號
- > signal<-function(buy, sell){}) # ...代碼省略
- # 交易信號數(shù)據(jù)
- > 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
利用交易信號數(shù)據(jù),進(jìn)行模擬交易。我們設(shè)定交易參數(shù)和規(guī)則:
- 以10萬元人民幣為本金
- 買入信號出現(xiàn)時(shí),以收盤價(jià)買入,每次買入價(jià)值1萬元的股票。如果連續(xù)出現(xiàn)買入信號,則一直買入。若現(xiàn)金不足1萬元時(shí),則跳過買入信號。
- 賣出信號出現(xiàn)時(shí),以收盤價(jià)賣出,一次性平倉信號對應(yīng)的股票。
- 手續(xù)費(fèi)為0元
- # 模擬交易
- > trade<-function(sdata,capital=100000,fixMoney=10000){}) # ...代碼省略
- # 交易結(jié)果
- > result<-trade(sdata,100000,10000)
來看一下,每筆交易的明細(xì)。
- > 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
一共發(fā)生了5筆交易,其中4筆買入,1筆賣出。最后,資金剩余103275.88元,賺了3275.88元,收益率3.275%。
在賣出時(shí),賺錢的交易有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
在賣出時(shí),賠錢的交易,沒有發(fā)生。
- > result$fall
- [1] Value ma20 dif sd rate op cash amount asset diff
- <0 行> (或0-長度的row.names)
接下來,我們再對比一下,資產(chǎn)凈值和股價(jià)。
- # 資產(chǎn)凈值曲線
- > 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'])) # 資產(chǎn)凈值曲線
剛才我們是對一支股票進(jìn)行了測試,發(fā)現(xiàn)是有機(jī)會(huì)的,那么我再換另外一支股票,看一下是否用同樣的效果呢?我們把剛才數(shù)據(jù)操作的過程,封裝到統(tǒng)一的quick函數(shù),就可以快速驗(yàn)證均值回歸在其他股票的表現(xiàn)情況了。
- > quick<-function(title,sDate,eDate){} # ...代碼省略
我們用樂視網(wǎng)(300104)試一下,看看有沒有賺錢的機(jī)會(huì)??!
- > title<-"300104.SZ"
- > sDate<-as.Date("2015-01-01") #開始日期
- > eDate<-as.Date("2015-07-10") #結(jié)束日期
- > 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)
從數(shù)據(jù)結(jié)果看,我們又賺到了。一共發(fā)生了8筆交易,其中7筆買入,1筆賣出。最后,資金剩余109438.80元,賺了9438.80元,收益率9.43%。
畫出交易信號圖
- > title<-"300104.SZ"
- > sDate<-as.Date("2015-01-01") #開始日期
- > eDate<-as.Date("2015-07-10") #結(jié)束日期
- > 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') #畫圖
在恐慌的6月份,當(dāng)別人都被套牢30%以上的情況下,我們還有9%正收益,那么應(yīng)該是多么舒心的一件事情?。。?/p>
#p#
3. 量化選股
上文中,我們用2支股票進(jìn)行了測試,發(fā)現(xiàn)均值回歸模型是適合于股票交易的。如果我們利用模型對全市場的股票進(jìn)行掃描,應(yīng)用會(huì)產(chǎn)生更多的交易信號,找到更多的投資機(jī)會(huì),這樣我們就能如何能獲得更大的收益。
那么,接下來我們就根據(jù)均值回歸的理論進(jìn)行量化選股。
根據(jù)我們之前的經(jīng)驗(yàn),當(dāng)股價(jià)與平均標(biāo)準(zhǔn)差的偏離越大,有可能帶來的收益就越大。那么通過量化的手段,在整個(gè)的市場2700多支股票中,把每天偏離最大股票的找出來進(jìn)行交易,就可以有效地分配我們的資金,進(jìn)行更有效的投資。我們要試一下,市場是否是和我們的思路是一致的。
對全市場股票進(jìn)行掃描,首先計(jì)算差值、平均值和平均標(biāo)準(zhǔn)差。
- > sDate<-as.Date("2015-01-01") # 開始日期
- > eDate<-as.Date("2015-07-10") # 結(jié)束日期
- # 計(jì)算差值、平均值和平均標(biāo)準(zhǔn)差
- > data0<-lapply(data,function(stock){}) # 代碼省略
- # 去掉空數(shù)據(jù)
- > 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支股票由于數(shù)據(jù)樣本不足被排除。
接下來,繼續(xù)對2697支股票進(jìn)行篩選,找到符合要求的買入信號點(diǎn)。
- # 計(jì)算買入信號
- > buys<-lapply(data0,function(stock){}) # ...代碼省略
- # 去掉空數(shù)據(jù)
- > 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
通過計(jì)算發(fā)現(xiàn),有1819支股票,在這半年中產(chǎn)生過買入信號。每支股票產(chǎn)生的買入信號的時(shí)間和頻率都是不同,這樣我們就可以把錢分散投資到不同的股票上,同時(shí)分散風(fēng)險(xiǎn)。如果交易信號同一天出現(xiàn)在多支的股票上,而我們資金有限,又想讓收益最大化,那么我們可以選擇偏離值最大的股票進(jìn)行交易。
接下來,我們用程序找到每日偏離最大的股票。
- # 合并數(shù)據(jù),從list轉(zhuǎn)型到data.frame
- buydf<-ldply(buys,function(e){}) # ...代碼省略
- # 選出同一日rate最大的股票,做為買入信號
- buydatas<-ddply(buydf, .(date), function(row){}) # ...代碼省略
- # 查看買入信號
- > nrow(buydatas)
- [1] 81
- # 查看買入信號細(xì)節(jié)
- > 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個(gè)買入信號點(diǎn),基本上每個(gè)交易日都是買入信號。有了買入信號,繼續(xù)找到賣出信號。
- # 賣出信號
- > 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
通過計(jì)算,一共有33個(gè)買出信號點(diǎn)。最后,合并買入信號和賣出信號,并計(jì)算收益。
- > buydatas$op<-'B' # 買入標(biāo)志
- > selldatas$op<-'S' # 賣出標(biāo)志
- > sdatas<-rbind(buydatas,selldatas) # 合并數(shù)據(jù)
- > row.names(sdatas)<-1:nrow(sdatas) # 重設(shè)行號
- > 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
最后,按照股票進(jìn)行分組,分別計(jì)算個(gè)股的收益。
- # 計(jì)算個(gè)股的收益
- > 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
通過優(yōu)化的規(guī)則設(shè)計(jì),一共有2筆交易,賺了495元。如要我們沒有進(jìn)行算法優(yōu)化,一直交易萬科A,那么會(huì)發(fā)生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
本文到此就要結(jié)束了!但其實(shí)還有很多的事情要做,比如對模型參數(shù)的優(yōu)化,用10日均線代替20日均線,用3倍標(biāo)準(zhǔn)差偏移代替2倍標(biāo)準(zhǔn)差偏移,對樣本進(jìn)行正態(tài)分布的檢驗(yàn),結(jié)合其他趨勢類模型共同產(chǎn)生信號等,這些就不是一篇文章可以解決的事情了。大家可以況客金融平臺(tái)的網(wǎng)站上,發(fā)現(xiàn)更多不一樣的策略。
本文從均值回歸的理論的介紹開始,到市場特征檢驗(yàn),再到數(shù)學(xué)公式,R語言建模,歷史數(shù)據(jù)回測,最后找到投資機(jī)會(huì),是一套完整的從理論到實(shí)踐的學(xué)習(xí)方法。雖然困難重重,但做為有理想的極客,我們是有能力來克服這些困難的。
4. 關(guān)于作者
張丹,況客科技(北京)有限公司,創(chuàng)始人/CTO。
《R的極客理想》系列圖書作者,個(gè)人博客: http://blog.fens.me。
從程序員開始到架構(gòu)師,再到金融量化創(chuàng)業(yè)者,倡導(dǎo)跨學(xué)科思維,多語言編程!本文同時(shí)用到了計(jì)算機(jī)、金融、數(shù)學(xué)、統(tǒng)計(jì)等多學(xué)科知識的結(jié)合,我認(rèn)為這是技術(shù)復(fù)合人才未來的發(fā)展方向。如果說過去10年是房地產(chǎn)的黃金10年,那么未來的10年將是金融的黃金10年。當(dāng)我們IT人掌握了足夠的金融知識,一定會(huì)有能力去金融市場搶錢的。
抓住機(jī)會(huì)??!程序員,加油!