[譯]量化投資教程:投資組合優化與R實踐

微信號: harryzhustudio 使用請聯繫作者。

概述

最近,我在研究投資組合優化的問題,主要針對的是股票持倉的組合優化,我們會在這個分析過程中發現一些有意思的現象,並一步一步優化、檢驗我們的風控模型。本文將有四個部分分別闡述具體步驟。

  • 在第一部分(原文)中,我將解釋什麼是杠鈴策略,並初步建立風控模型,比較持倉策略和風險收益的關係。

  • 在第二部分(原文)中,我將解釋什麼是無風險利率假定,討論多項式擬合的情形。

  • 在第三部分(原文)中,我將解釋如何通過放鬆約束最優化求解過程以避免非凹的情形,並做了實例演示。

  • 在第四部分(原文)中,我將對比大盤策略、等權策略以及之前的優化策略之間的優劣。

請注意,本文不應該被作為投資建議。本文數據是基於之前觀察到的收益來模擬的,和歷史上的數據並不太一致。這些技術可以幫助了解如何更好地分配一個投資組合。它不應該被用作唯一的投資決策,如果你正在尋找的建議應該找到一個合格的專業機構。

第一部分

數字特徵計算

當看到三個政府 ETF 債券(TLT、IEF、SHY)調整後的股息回報率,我注意到中間到期債券(IEF)風險收益情況比長期債券(TLT)更好。我以表格形式顯示結果。在本文中,我們將重新分析和圖形化展示我們的結果:

首先,用如下函數來獲取ETF的回報序列

require(fImport)nrequire(PerformanceAnalytics)nn# 將股票數據載入到一個時間序列對象的函數nimportSeries = function (symbol,from,to) {nn # 從雅虎讀取金融數據n input = yahooSeries(symbol,from=from,to=to)nn # 列名n adjClose = paste(symbol,".Adj.Close",sep="")n inputReturn = paste(symbol,".Return",sep="")n CReturn = paste(symbol,".CReturn",sep="")nn # 計算收益率並生成時間序列n input.Return = returns(input[,adjClose])n colnames(input.Return)[1] = inputReturnn input = merge(input,input.Return)nn # 計算累積收益率並生成時間序列n input.first = input[,adjClose][1]n input.CReturn = fapply(input[,adjClose],FUN=function(x) log(x) - log(input.first))n colnames(input.CReturn)[1] = CReturnn input = merge(input,input.CReturn)nn # 刪掉一些沒用的東西,如果你不知道就不用刪除n rm(input.first,input.Return,input.CReturn,adjClose,inputReturn,CReturn)nn # 返回時間序列n return(input)n}n

計算年化收益、標準差和夏普率。

# 獲取短中期和長期政府債券的收益率序列nfrom = 「2001-01-01″nto = 「2011-12-16″ntlt = importSeries(「tlt」,from,to)nshy = importSeries(「shy」,from,to)nief = importSeries(「ief」,from,to)nmerged = merge(tlt,shy)nmerged = merge(merged,ief)nvars = c(「tlt.Return」,「shy.Return」,「ief.Return」)n# 計算年回報率nt = table.AnnualizedReturns(merged[,vars],Rf=mean(merged[,「shy.Return」],na.rm=TRUE))ntn

結果如下

杠鈴策略

如果你經常看娛樂投資電視台,你最終會聽到「杠鈴策略」這個術語。這是指一個極端的投資組合分配方案。所有的權重都是極端情況,你可以想像這是一個杠鈴。在政府債券的投資組合,這將意味著購買期限長或短而不是持有中間。那麼什麼樣的風險收益情況下你會採用這個策略?

首先,我們將風險定義為投資組合的方差。有各種各樣的理由不使用方差,但它是從最古老的50年代開始這種類型的分析都是全新的。我們將定義收益為預期收益。在上面的表中,年回報率表示持有資產的預期收益為1年,標準差的平方表示風險。

假設投資組合只包括持有長期和短期債券,我們便需要計算投資組合的預期收益和風險。收益的計算是很容易的,這是兩種持倉的加權平均收益,權重就是每個資產的投入資本百分比。

RP = WTLT* RTLT + WSHY * RSHYnWhere: WTLT + WSHY= 1n

顯然這兩種資產具有相關性(在馬科維茨於1952年的博士論文發表之前,投資經理不了解相關性並且默認假設為1 -馬科維茨因此獲得了諾貝爾獎)。假設回報是正態分布的,那麼投資組合方差將是:

Vp = WTLT 2*σ2TLT+ WSHY 2* σ2SHY + WTLT* WSHY * σTLT * σSHY *CorrTLT,SHYnWhere: WTLT+ WSHY = 1n

風控模型

我們可以根據這兩個部分的知識改變持倉權重並為我們的杠鈴策略建立風險收益模型。

# 檢查相關性ncorr = cor(merged[,vars],use=『complete.obs』)nc = corr[『tlt.Return』,『shy.Return』]n# 假設一個杠鈴策略是持有長期和短期資產n# 定義風險、收益nws = NULLnwt = NULLnmu = NULLnsigma = NULLn# 50個觀察nn=50n# 遍歷杠鈴策略的權重nfor (i in 0:n){n wsi = i/n;n wti = 1–wsi;n n mui = wsi * rSHY + wti * rTLTn sigmai = wsi*wsi*sSHY*sSHY + wti*wti*sTLT*sTLT + wsi*wti*sSHY*sTLT*cn n n ws = c(ws,wsi)n wt = c(wt,wti)n mu = c(mu,mui)n sigma = c(sigma,sigmai)n}n# 風險收益的數據集nrrProfile = data.frame(ws=ws,wt=wt,mu=mu,sigma=sigma)n# 繪圖nplot(rrProfile$sigma,n rrProfile$mu,n xlim=c(0,.022),n ylim=c(0,.08),n ylab=「Expected Yearly Return」,n xlab=「Expected Yearly Variance」,n main=「Efficient Frontier for Government Bond Portfolios」)n

注意,上面的方程是二次的。我們可以配合我們剛剛創建的點畫出拋物線。注意,而習慣上把風險放在X軸上,而把擬合方差(風險)作為因變數放在Y軸。

# 為模型擬合一個二次函數nfit = lm(rrProfile$sigma ~ rrProfile$mu + I(rrProfile$mu^2))n

接下來,我們圖上添加擬合線。

# 得到回歸係數ncoe = fit$coefficientsn# 得到每個回歸預測的風險值nmuf = NULLnsfit = NULLnfor (i in seq(0,.08,by=.001)){n muf = c(muf,i)n n s = coe[1] + coe[2]*i + coe[3]*i^2n sfit = c(sfit,s)n}n# 畫出預測邊值nlines(sfit,muf,col=『red』)n

tseries包中的portfolio.optim也許是一個更好的選擇。我們只需要輸入預期收益率,它會直接返回出來最優組合權重。我們可以在最低預期回報率(比如 100% 持有 SHY)到最高預期回報率(比如 100% 持有 TLT)之間修改輸入的收益。注意,portfolio.optim使用日回報率做計算,因此代碼將不得不做一些處理。我們假設一年255個交易日。

# 現在讓我們添加第三個標的。n# 除非我們想做一個格點搜索,否則我們需要對每個級別的回報減少風險來優化投資組合。n# portfolio.optim 在時間序列中不能有 NA 值。nnm2 = removeNA(merged[,vars])nwSHY = NULLnwIEF = NULLnwTLT = NULLner = NULLneStd = NULLnn# 在回報水平之間不斷循環搜索找到最優的投資組合,包括最小值(rSHY)和最大值(rTLT)n# portfolio.optim 使用日回報數據,因此我們不得不做出相應的調整nnfor (i in seq((rSHY+.001),(rTLT–.001),length.out=100)){n pm = 1+in pm = log(pm)/255n opt = portfolio.optim(m2,pm=pm)n er = c(er,exp(pm*255)–1)n eStd = c(eStd,opt$ps*sqrt(255))n wTLT = c(wTLT,opt$pw[1])n wSHY = c(wSHY,opt$pw[2])n wIEF = c(wIEF,opt$pw[3])n}nn# 畫出三個標的的有效邊界。nnlines(eStd^2,er,col=『blue』)nlegend(.014,0.015,c(「『Barbell』 Strategy」,「All Assets」),n col=c(「red」,「blue」),n lty=c(1,1))nsolution = data.frame(wTLT,wSHY,wIEF,er,eStd)n

結論

如下圖:

總資產組合中有效邊界的藍線表示其優於杠鈴策略。對於每個風險水平,預期回報都是更高的。從圖表上看,這表明添加 IEF 到組合將優化組合。進一步,我們看到杠鈴策略回報的逼近最大值,用三個標的組合的組合策略比之前的風險少了一半。

相關代碼

第二部分

在前面的文章中,我們構建了一個投資組合的有效邊界的債券,下一步,我們要找到超級有效的(或市場)的投資組合。如果您有不熟悉的概念,第二部分可以在維基百科上參考一些資料。

無風險利率假定

如果你不願意看維基百科,我也會解釋相關概念的。如果你有一個保底回報率(無風險利率),那麼資產位於圖表的y軸。在邊界的切點處畫一條切線,切點代表著非常有效的投資組合。你可以混合持有一定權重的組合標的和無風險資產,實現比邊界曲線更好的風險回報比。

明白了嗎?非常棒!

所以我們需要找到線和切點。首先,讓我們假定一個無風險利率。有些人會使用3個月的國債收益率。為了和數據匹配,我們需要將它處理成一年期的。我的銀行給我一個2%的年保底收益率,所以我將用2%。

多項式擬合

我們如何找到切點?當我們有兩個標的時,我們知道我們有一個二階多項式。當我們有三個標的時有一些存在缺陷的面(非凸時求極值較困難),在這種情況下我們停止投資 SHY,轉向投資 TLT。我們可以擬合高階多項式,但我們不能確保我們有一個凹面。或者我可以說,我們不能保證我們的切點總是高於邊值。同樣地,我們也可以想像一下二次的情形或許有切點存在負值。

作為一個例子,這裡雖然六階多項式的擬合符合缺陷,但我們的切線點不是有用的。

只有一個實根,其餘的都是虛根,我們需要另一種方法。

我們可以為第一部分里的邊值擬合一個多項式;此時在持倉組合中只有 SHY 和 IEF。雖然這樣也行得通,但是這不太通用。我想找到一個可以不管是什麼邊值形狀都適用的通用解決方案。下個部分,我們會繼續討論這個問題。

第三部分

上一節,我們討論了用擬合曲線尋找有效邊值來建立投資組合所存在的問題。由於邊值存在的缺陷,我們不能保證你和曲線在投資組合的解空間內是凹的。我們需要其他方法來解決這個問題。

放鬆約束

本文所用方法是在無風險利率和每個邊值之間都畫一條線來計算這條線和邊值的差值是多少。資本市場線應該是不超過所有邊值的。

CMLi <= EFin

我們所找到的這個邊值的尺度意味著我們也許不能找到準確地市場投資組合。為避開這一點,我放鬆了上述約束:

Portfolioj that max Count(CMLi < EFi)n

我整理以下R函數。注意,我已經轉向使用標準差作為風險度量尺度,這是更傳統的選擇。

marketPortfolio = function(merged,rf,returnNames, weightNames,graph=FALSE){n n # 為投資組合創建空數據框以初始化n weights = data.frame(t(rep(NA,length(weightNames))))n colnames(weights) = weightNamesn weights = weights[–1,]n # 計算年化收益n t = table.AnnualizedReturns(merged[,returnNames])n n # 優化範圍n maxRet = max(t[『Annualized Return』,]) – .005n minRet = min(t[『Annualized Return』,]) + .005n n #portfolio.optim 沒有 NA 值,進行過濾n m2 = removeNA(merged[,returnNames])n n er = NULLn eStd = NULLn n # 在每個收益水平上循環搜索最優組合n # portfolio.optim 是日收益,做出相應調整nn for (i inseq(minRet,maxRet,length.out=500)){n pm = 1+in pm = log(pm)/255n opt = portfolio.optim(m2,pm=pm)n er = c(er,exp(pm*255)–1)n eStd = c(eStd,opt$ps*sqrt(255))n w = t(opt$pw)n colnames(w) = weightNamesn weights = rbind(weights,w)n }n n solution = weightsn solution$er = ern solution$eStd = eStdn n #找到最小 Std 和最大 Er 的下標n minIdx = which(solution$eStd == min(solution$eStd))n maxIdx = which(solution$er == max(solution$er))n n # 獲取結果子集n subset = solution[minIdx:maxIdx,c(「er」,「eStd」)]n subset$nAbove = NAn n #對於子集中的每一個值, 計算點的總數,在下面的點和RF資產之間畫線n for (i in seq(1,maxIdx–minIdx+1)){n toFit = data.frame(er=rf,eStd=0)n toFit = rbind(toFit,subset[i,c(「er」,「eStd」)])n fit = lm(toFit$er ~ toFit$eStd)n poly = polynomial(coef = fit$coefficients)n toPred = subsetn colnames(toPred) = c(「actEr」,「eStd」)n toPred$er = predict(poly,toPred[,「eStd」])n toPred$diff = toPred$er – toPred$actErn subset[i,「nAbove」] = nrow(toPred[which(toPred$diff > 0),])n }n n # 得到切點n # 線以下是最大化n max = max(subset$nAbove)n er = subset[which(subset$nAbove == max),「er」]n eStd = subset[which(subset$nAbove == max),「eStd」]n n # 市場投資組合的下標n idx = which(solution$er == er & solution$eStd == eStd)n n # 畫線n if (graph){n maxStd = max(solution$eStd) + .02n maxRetg = max(solution$er) + .02n plot(solution$eStd,n solution$er,n xlim=c(0,maxStd),n ylim=c(0,maxRetg),n ylab=「Expected Yearly Return」,n xlab=「Expected Yearly Std Dev」,n main=「Efficient Frontier」,n col=「red」,n type=「l」,n lwd=2)n abline(v=c(0), col=「black」, lty=「dotted」)n abline(h=c(0), col =「black」, lty=「dotted」)n n toFit = data.frame(er=rf,eStd=0)n toFit = rbind(toFit,solution[idx,c(「er」,「eStd」)])n fit = lm(toFit$er ~ toFit$eStd)n abline(coef=fit$coefficients,col=「blue」,lwd=2)n }n n # 返回投資組合權重、eStd 和 eRn out = solution[idx,]n return (out)n}n

例子

讓我們使用埃克森美孚(XOM),IBM(IBM),中期政府債券ETF(IEF)這個組合做測試。這裡假定你有importSeries()函數的定義。

require(polynom)nrequire(fImport)nrequire(PerformanceAnalytics)nrequire(tseries)nrequire(stats)nfrom = 「2003-01-01″nto = 「2011-12-16″nxom = importSeries(「xom」,from,to)nibm = importSeries(「ibm」,from,to)nief = importSeries(「ief」,from,to)nmerged = merge(xom,ibm)nmerged = merge(merged,ief)nvars = c(「xom.Return」,「ibm.Return」,「ief.Return」)nvars2 = c(「xom」,「ibm」,「ief」)nmp = marketPortfolio(merged,.02,vars,vars2,graph=TRUE)nmpn

日誌的輸出是:

xomibmiefereStd0.093950.13780.76820.077620.05996

創建的圖:

結論

這個投資組合優化的給了我們一個發現更低邊界例子。這是一個不正常的現象。

這就是為什麼我們結果子集只包括部分邊界的頂點(min(StdDev))和最大的回報。因為我們發現邊界最小收益到最大的收益,我們保證序列是有序的,所以只考慮了上部邊界。

一個更精確的方法是找到的區域包含市場組合的邊值然後用網格搜索尋找最優投資組合。上節我們討論了在一個範圍中擬合曲線的方法。如果有需求,我也可以用上面的方法再做一次。出於演示目的,我想我們應該足夠了。

第四部分

這節將對投資組合優化系列做一個總結,我們將基於組合優化和測試結果對CAPM市場投資組合構建一個交易策略。

值得重申的是:

我所說不應該被當做投資建議。這些結果是基於之前觀察到的收益並且是一些模擬值。這些技術可以幫助了解如何更好地分配一個投資組合。它不應該被當作是唯一的投資決策。如果你正在尋找的建議,還是找一個合格的專家比較好。

在馬科維茨的工作的基礎上,特雷諾,夏普等人開發了資本資產定價模型(CAPM)。在1990年,他們因為這項工作與馬科維茨共同獲得了諾貝爾獎。CAPM是一個一般均衡模型。模型假定市場價格反映了所有可獲得的信息並且反映一個標的的「公平」價值。在此基礎上,市場投資組合可以證明是市值加權組合。市值(或市值)被定義為股價乘以流通股的數量,也就是公司的股本總額。公司的權重是該公司市值除以所有證券的總市值。

資本加權指標和指數基金已成為標準。標準普爾是大多數人考慮的標準「市場投資組合」。我們將參考一個市值加權策略對我們的投資組合優化策略進行測試。

現在的CAPM還存在諸多漏洞,有很多方法都能發現這些問題。一種方法是說現在的價格不是公允價值,而是將均值當作公允價值。在這種情況下,當價格高於公允價值,市值加權組合將對過定價過高的證券資產給予過大的權重。當它用均值取代後, 投資組合的表現將由於權重超標而變差。

這個理論是著名的羅伯特?阿諾特提出的。我強烈推薦這本書,《基本面指數:一種更好的投資方式》。他認為,任何打破用價格打破相關性的投資組合策略隨著時間的推移都將跑贏資本化指數。他在書中提到他創造了一個新的指數,他簡單地假定每個標的都是等權重的(標準普爾發布了這個指數)。正因為如此,我們還將在標的同等權重條件下測試我們的策略。

組合優化策略

這是我們的投資組合優化策略:

  1. 每個季度初,用上一季度收益計算市場投資組合。

  2. 對當前季度使用當前組合。

  3. 下個季度的開始,循環回到第一步

  4. 在我們的投資組合中至少需要3個股票。

  5. 沒有做空。

  6. 用2%作為無風險利率。

  7. 每次分析的第一個季度如果優化失敗就使用同等權重的投資組合。

當價格走勢是按季度選取的這種策略往往會跑贏大盤。比如如果上個季度的收益和波動性可以準確預測本季度的值的情況就是這樣。此外,我們也不考慮交易成本。而且,2%的無風險利率是靜態的,嚴格的說,我們應該在每個季度開始時使用3個月國債的利率。這就是為什麼這只是一個例子,我們假定了很多美好的假設。

首先,我們需要修改我們以前創建的marketPortfolio()函數。你可以在這裡找到它。新函數:

marketPortfolio = function(merged, rf, returnNames, weightNames,graph=FALSE, points=500, maxWeight=.334, Debug=FALSE)n

改進之處

我們的改進之處:

  1. 如果我們需要可以添加一個調試選項列印輸出

  2. 增加容錯功能。有時直接得到一個可行解是不可能的,這個函數需要相應的檢測並且處理錯誤。

  3. 資本的最大回報我們限定在50%以下,這個值太大會導致其他奇怪的行為。

  4. 同樣,把最小收益的下界定在.005%。

  5. 如果最大收益是< 0,那麼簡單地找到最小方差投資組合。

  6. 添加一個maxWeight選項,讓我們限制每個證券標的的權重。

我們考慮的股票池現在是28個道瓊斯成分股(因為某些原因雅虎金融值提供了28隻而不是30隻)。我預先下載並存儲這些收益值為一個 .rda 文件。你可以在這裡得到它。我計算每個股票的初始市值權重並存儲為 .csv 文件。你可以在這裡得到它。

# 讀取已存的收益值nload(D:WorkspacePortfolioOptimizationreturns.rda)nreturns = resultsnrm(results)nstocks = colnames(returns)n# 獲取大盤權重nstockWgt = read.table(D:WorkspacePortfolioOptimizationstocks.csv,n header=TRUE,sep=,)[,Weight]n# 計算大盤權重投資組合的回報nresults = as.matrix(returns) %*% stockWgtncolnames(results) = c(CapWeight Portfolio)nresults = as.timeSeries(results)n# 計算等權重投資組合的回報nret = t(as.matrix(rep(1/length(stocks),length(stocks))))nresEqual = as.matrix(returns) %*% t(ret)ncolnames(resEqual) = EqualWeight Portfolion# 匯總結果到一個時間序列對象中nresults = cbind(results,resEqual)n# 獲取收益值的日期序列ndates = time(returns)ndates = dates@Datan# 從日期計算季度nqtrs = quarters(dates)nqtrs = cbind(dates,qtrs)nkeep = NULLnlastQtr = nnn#遍曆日期和季度序列,只保留每個季度第一天的日期nfor (i in seq(1,nrow(qtrs))){n if (qtrs[i,2] == lastQtr){n if (i == nrow(qtrs)){n keep = c(keep,1)n } else {n keep = c(keep,0)n }n }else {n keep = c(keep,1)n }n n lastQtr = qtrs[i,2]n}nqtrs = cbind(qtrs,keep)nn# 獲取每個季度第一天的下標nindx = which(qtrs[,3] == 1)n# 對每個周期的第一個季度,使用等權策略nres = as.matrix(returns[indx[1]:(indx[2]1),]) %*% t(ret)n#對每個周期基於上一季度的數據進行循環計算大盤組合的表現nfor (i in seq(2,length(indx)1)){n print(Running )n print(i)n n # 得到上季度股票回報的子集 n subset = returns[indx[i1]:(indx[i]1),]n s = start(subset)n e = end(subset)n print(Fitting for:)n print(s)n print(e)n n # 計算大盤投資組合n mp = marketPortfolio(subset,.02,stocks,stocks,n graph=TRUE,n points=500,n Debug=FALSE)n n #如果優化失敗,使用等權策略n if (is.null(mp)){n ret = t(as.matrix(rep(1/length(stocks),length(stocks))))n } else {n ret = as.matrix(mp[,stocks])n }n n # 本季度的子集n subRes = returns[indx[i]:(indx[i+1]1),]n s = start(subRes)n e = end(subRes)n print(Calculating Returns for:)n print(s)n print(e)n n # 計算當前季度的大盤策略的收益並追加在收益序列後面n subRes = as.matrix(subRes) %*% t(ret)n res = rbind(res,subRes)n}nn# 循環計算時,序列的最後一天不計算收益nsubRes = returns[nrow(returns),]nsubRes = as.matrix(subRes) %*% t(ret)nres = rbind(res,subRes)n# 添加組合優化策略ncolnames(res) = Portfolio Optimizationnres = as.timeSeries(res)nresults = cbind(results,res)n#計算年化收益統計特徵ntable.AnnualizedReturns(results)n#計算並繪製相關性npng(d:WorkspacePortfolioOptimizationmpCorr.png)nchart.Correlation(results,histogram=TRUE,pch=+)ndev.off();n##計算並繪製累積收益npng(d:WorkspacePortfolioOptimizationmpReturns.png)nchart.CumReturns(results,n main=Total Returns CapWeight vs PortOpt,n legend.loc=topleft)ndev.off()n

我們的年回報率表:

結論

我們的投資組合優化策略優於大盤權重策略,但跑輸了等權重策略。如果你支持阿諾特的話就覺得這沒什麼奇怪的了,這只是因為我們沒有打破價格的相關性罷了。

這是相關性的圖表:

我們已經創建了一個和大盤權重策略非常相關的策略,但是還是不如等權策略。等權策略和大盤權重策略的關聯度是非常有趣的。

這是收益繪製的時間序列:

有趣的是,可以看到在圖中綠色的部分顯示我們的投資組合在2009年3月份的市場底部開始有一個快速反彈。這大大跑贏了大盤權重組合。

作為分享主義者(sharism),本人所有互聯網發布的圖文均遵從CC版權,轉載請保留作者信息並註明作者 Harry Zhu 的 FinanceR專欄:FinanceR - SegmentFault,如果涉及源代碼請註明GitHub地址:github.com/harryprince。微信號: harryzhustudio

商業使用請聯繫作者。

推薦閱讀:

現代投資組合理論的發展
耶魯模式成功在何處?
在資金管理方面你都有哪些有價值的經驗?

TAG:投资组合 | R编程语言 | 投资 |