譯文 | 量化投資教程:投資組合優化與R實踐
本文由CDA作者庫成員HarryZhu翻譯,並授權釋出。 CDA作者庫凝聚原創力量,只做更有價值的分享。
Harry Zhu,擅長用Python和R進行資料建模、定量研究,目前就職於量子金服(Quantum Financial Service)
歡迎關注個人主頁:
https://github.com/harryprince
https://segmentfault.com/u/harryprince
概述
最近,我在研究投資組合優化的問題,主要針對的是股票持倉的組合優化,我們會在這個分析過程中發現一些有意思的現象,並一步一步優化、檢驗我們的風控模型。本文將有四個部分分別闡述具體步驟。
- 在第一部分(原文)
- 在第二部分(原文)中,我將解釋什麼是無風險利率假定,討論多項式擬合的情形。
- 在第三部分(原文)中,我將解釋如何通過放鬆約束最優化求解過程以避免非凹的情形,並做了例項演示。
- 在第四部分(原文)中,我將對比大盤策略、等權策略以及之前的優化策略之間的優劣。
請注意,本文不應該被作為投資建議。本文資料是基於之前觀察到的收益來模擬的,和歷史上的資料並不太一致。這些技術可以幫助瞭解如何更好地分配一個投資組合。它不應該被用作唯一的投資決策,如果你正在尋找的建議應該找到一個合格的專業機構。
第一部分
數字特徵計算
當看到三個政府 ETF 債券(TLT、IEF、SHY)調整後的股息回報率,我注意到中間到期債券(IEF)風險收益情況比長期債券(TLT)更好。我以表格形式顯示結果。在本文中,我們將重新分析和圖形化展示我們的結果:
首先,用如下函式來獲取ETF的回報序列
require(fImport) require(PerformanceAnalytics) # 將股票資料載入到一個時間序列物件的函式 importSeries = function (symbol,from,to) { # 從雅虎讀取金融資料 input = yahooSeries(symbol,from=from,to=to) # 列名 adjClose = paste(symbol,".Adj.Close",sep="") inputReturn = paste(symbol,".Return",sep="") CReturn = paste(symbol,".CReturn",sep="") # 計算收益率並生成時間序列 input.Return = returns(input[,adjClose]) colnames(input.Return)[1] = inputReturn input = merge(input,input.Return) # 計算累積收益率並生成時間序列 input.first = input[,adjClose][1] input.CReturn = fapply(input[,adjClose],FUN=function(x) log(x) - log(input.first)) colnames(input.CReturn)[1] = CReturn input = merge(input,input.CReturn) # 刪掉一些沒用的東西,如果你不知道就不用刪除 rm(input.first,input.Return,input.CReturn,adjClose,inputReturn,CReturn) # 返回時間序列 return(input) }
計算年化收益、標準差和夏普率。
# 獲取短中期和長期政府債券的收益率序列
from = “2001-01-01″
to = “2011-12-16″
tlt = importSeries(“tlt”,from,to)
shy = importSeries(“shy”,from,to)
ief = importSeries(“ief”,from,to)
merged = merge(tlt,shy)
merged = merge(merged,ief)
vars = c(“tlt.Return”,“shy.Return”,“ief.Return”)
# 計算年回報率
t = table.AnnualizedReturns(merged[,vars],Rf=mean(merged[,“shy.Return”],na.rm=TRUE))
t
結果如下:
槓鈴策略
如果你經常看娛樂投資電視臺,你最終會聽到“槓鈴策略”這個術語。這是指一個極端的投資組合分配方案。所有的權重都是極端情況,你可以想象這是一個槓鈴。在政府債券的投資組合,這將意味著購買期限長或短而不是持有中間。那麼什麼樣的風險收益情況下你會採用這個策略?
首先,我們將風險定義為投資組合的方差。有各種各樣的理由不使用方差,但它是從最古老的50年代開始這種型別的分析都是全新的。我們將定義收益為預期收益。在上面的表中,年回報率表示持有資產的預期收益為1年,標準差的平方表示風險。
假設投資組合只包括持有長期和短期債券,我們便需要計算投資組合的預期收益和風險。收益的計算是很容易的,這是兩種持倉的加權平均收益,權重就是每個資產的投入資本百分比。
RP = WTLT* RTLT + WSHY * RSHY
Where: WTLT + WSHY= 1
顯然這兩種資產具有相關性(在馬科維茨於1952年的博士論文發表之前,投資經理不瞭解相關性並且預設假設為1 -馬科維茨因此獲得了諾貝爾獎)。假設回報是正態分佈的,那麼投資組合方差將是:
Vp = WTLT 2*σ2TLT+ WSHY 2* σ2SHY + WTLT* WSHY * σTLT * σSHY *CorrTLT,SHY
Where: WTLT+ WSHY = 1
結論
如下圖:
總資產組合中有效邊界的藍線表示其優於槓鈴策略。對於每個風險水平,預期回報都是更高的。從圖表上看,這表明新增 IEF 到組合將優化組合。進一步,我們看到槓鈴策略回報的逼近最大值,用三個標的組合的組合策略比之前的風險少了一半。
相關程式碼:
require(fImport)
require(PerformanceAnalytics)
require(tseries)
require(stats)
options(scipen=100)
options(digits=4)
#Function to load stock data into a Time Series object
importSeries = function (symbol,from,to) {
#Read data from Yahoo! Finance
input = yahooSeries(symbol,from=from,to=to)
#Character Strings for Column Names
adjClose = paste(symbol,".Adj.Close",sep="")
inputReturn = paste(symbol,".Return",sep="")
CReturn = paste(symbol,".CReturn",sep="")
#Calculate the Returns and put it on the time series
input.Return = returns(input[,adjClose])
colnames(input.Return)[1] = inputReturn
input = merge(input,input.Return)
#Calculate the cumulative return and put it on the time series
input.first = input[,adjClose][1]
input.CReturn = fapply(input[,adjClose],FUN=function(x) log(x) - log(input.first))
colnames(input.CReturn)[1] = CReturn
input = merge(input,input.CReturn)
#Deleting things (not sure I need to do this, but I can't not delete things if
# given a way to...
rm(input.first,input.Return,input.CReturn,adjClose,inputReturn,CReturn)
#Return the timeseries
return(input)
}
#Get Return Series for Short Medium and Long Term Gov Bonds
from = "2001-01-01"
to = "2011-12-16"
tlt = importSeries("tlt",from,to)
shy = importSeries("shy",from,to)
ief = importSeries("ief",from,to)
merged = merge(tlt,shy)
merged = merge(merged,ief)
vars = c("tlt.Return","shy.Return","ief.Return")
#Calculate Annualized Returns
t = table.AnnualizedReturns(merged[,vars],Rf=mean(merged[,"shy.Return"],na.rm=TRUE))
t
#Get the annualized return and StdDev for each series from the table
rTLT = t['Annualized Return',"tlt.Return"]
rSHY = t['Annualized Return',"shy.Return"]
rIEF = t['Annualized Return','ief.Return']
sTLT = t['Annualized Std Dev',"tlt.Return"]
sSHY = t['Annualized Std Dev',"shy.Return"]
sIEF = t['Annualized Std Dev',"ief.Return"]
#Check the correlations
corr = cor(merged[,vars],use='complete.obs')
c = corr['tlt.Return','shy.Return']
#Assuming a barbell strategy of long and short holdings, what is the risk return profile
ws = NULL
wt = NULL
mu = NULL
sigma = NULL
#50 observations
n=50
#Loop through the weights of the barbell
for (i in 0:n){
wsi = i/n;
wti = 1-wsi;
mui = wsi * rSHY + wti * rTLT
sigmai = wsi*wsi*sSHY*sSHY + wti*wti*sTLT*sTLT + wsi*wti*sSHY*sTLT*c
ws = c(ws,wsi)
wt = c(wt,wti)
mu = c(mu,mui)
sigma = c(sigma,sigmai)
}
#Risk Return Profile Data Frame
rrProfile = data.frame(ws=ws,wt=wt,mu=mu,sigma=sigma)
#Plot the profile
plot(rrProfile$sigma,
rrProfile$mu,
xlim=c(0,.022),
ylim=c(0,.08),
ylab="Expected Yearly Return",
xlab="Expected Yearly Variance",
main="Efficient Frontier for Government Bond Portfolios")
#Fit a quadratic function to the profile
fit = lm(rrProfile$sigma ~ rrProfile$mu + I(rrProfile$mu^2))
#How is the fit?
summary(fit)
#get the coefficients
coe = fit$coefficients
#Get predicted values risk values for each return
muf = NULL
sfit = NULL
for (i in seq(0,.08,by=.001)){
muf = c(muf,i)
s = coe[1] + coe[2]*i + coe[3]*i^2
sfit = c(sfit,s)
}
#plot the predicted frontier
lines(sfit,muf,col='red')
#now let's add the 3rd asset. Unless we want to do a grid search, we need
#to optimize the portfolio, minimizing the risk for each level of return
#portfolio.optim cannot have NA values in the time series, filter them out
m2 = removeNA(merged[,vars])
wSHY = NULL
wIEF = NULL
wTLT = NULL
er = NULL
eStd = NULL
#loop through finding the optimum portfolio for return levels between
#the minimum (rSHY) and the max(rTLT)
#
#portfolio.optim uses daily returns, so we have to adjust accordingly
for (i in seq((rSHY+.001),(rTLT-.001),length.out=100)){
pm = 1+i
pm = log(pm)/255
opt = portfolio.optim(m2,pm=pm)
er = c(er,exp(pm*255)-1)
eStd = c(eStd,opt$ps*sqrt(255))
wTLT = c(wTLT,opt$pw[1])
wSHY = c(wSHY,opt$pw[2])
wIEF = c(wIEF,opt$pw[3])
}
#Plot the efficient frontier for the 3 assets.
lines(eStd^2,er,col='blue')
legend(.014,0.015,c("'Barbell' Strategy","All Assets"),
col=c("red","blue"),
lty=c(1,1))
solution = data.frame(wTLT,wSHY,wIEF,er,eStd)
第二部分
在前面的文章中,我們構建了一個投資組合的有效邊界的債券,下一步,我們要找到超級有效的(或市場)的投資組合。如果您有不熟悉的概念,第二部分可以在維基百科上參考一些資料。
無風險利率假定
如果你不願意看維基百科,我也會解釋相關概念的。如果你有一個保底回報率(無風險利率),那麼資產位於圖表的y軸。在邊界的切點處畫一條切線,切點代表著非常有效的投資組合。你可以混合持有一定權重的組合標的和無風險資產,實現比邊界曲線更好的風險回報比。
明白了嗎?非常棒!
所以我們需要找到線和切點。首先,讓我們假定一個無風險利率。有些人會使用3個月的國債收益率。為了和資料匹配,我們需要將它處理成一年期的。我的銀行給我一個2%的年保底收益率,所以我將用2%。
多項式擬合
我們如何找到切點?當我們有兩個標的時,我們知道我們有一個二階多項式。當我們有三個標的時有一些存在缺陷的面(非凸時求極值較困難),在這種情況下我們停止投資 SHY,轉向投資 TLT。我們可以擬合高階多項式,但我們不能確保我們有一個凹面。或者我可以說,我們不能保證我們的切點總是高於邊值。同樣地,我們也可以想象一下二次的情形或許有切點存在負值。
作為一個例子,這裡雖然六階多項式的擬合符合缺陷,但我們的切線點不是有用的。
只有一個實根,其餘的都是虛根,我們需要另一種方法。
我們可以為第一部分裡的邊值擬合一個多項式;此時在持倉組合中只有 SHY 和 IEF。雖然這樣也行得通,但是這不太通用。我想找到一個可以不管是什麼邊值形狀都適用的通用解決方案。下個部分,我們會繼續討論這個問題。
第三部分
上一節,我們討論了用擬合曲線尋找有效邊值來建立投資組合所存在的問題。由於邊值存在的缺陷,我們不能保證你和曲線在投資組合的解空間內是凹的。我們需要其他方法來解決這個問題。
放鬆約束
本文所用方法是在無風險利率和每個邊值之間都畫一條線來計算這條線和邊值的差值是多少。資本市場線應該是不超過所有邊值的。
CMLi <= EFi
我們所找到的這個邊值的尺度意味著我們也許不能找到準確地市場投資組合。為避開這一點,我放鬆了上述約束:
Portfolioj that max Count(CMLi < EFi)
我整理以下R函式。注意,我已經轉向使用標準差作為風險度量尺度,這是更傳統的選擇。
marketPortfolio = function(merged,rf,returnNames, weightNames,graph=FALSE){
# 為投資組合建立空資料框以初始化
weights = data.frame(t(rep(NA,length(weightNames))))
colnames(weights) = weightNames
weights = weights[–1,]
# 計算年化收益
t = table.AnnualizedReturns(merged[,returnNames])
# 優化範圍
maxRet = max(t[‘Annualized Return’,]) – .005
minRet = min(t[‘Annualized Return’,]) + .005
#portfolio.optim 沒有 NA 值,進行過濾
m2 = removeNA(merged[,returnNames])
er = NULL
eStd = NULL
# 在每個收益水平上迴圈搜尋最優組合
# portfolio.optim 是日收益,做出相應調整
for (i inseq(minRet,maxRet,length.out=500)){
pm = 1+i
pm = log(pm)/255
opt = portfolio.optim(m2,pm=pm)
er = c(er,exp(pm*255)–1)
eStd = c(eStd,opt$ps*sqrt(255))
w = t(opt$pw)
colnames(w) = weightNames
weights = rbind(weights,w)
}
solution = weights
solution$er = er
solution$eStd = eStd
#找到最小 Std 和最大 Er 的下標
minIdx = which(solution$eStd == min(solution$eStd))
maxIdx = which(solution$er == max(solution$er))
# 獲取結果子集
subset = solution[minIdx:maxIdx,c(“er”,“eStd”)]
subset$nAbove = NA
#對於子集中的每一個值, 計算點的總數,在下面的點和RF資產之間畫線
for (i in seq(1,maxIdx–minIdx+1)){
toFit = data.frame(er=rf,eStd=0)
toFit = rbind(toFit,subset[i,c(“er”,“eStd”)])
fit = lm(toFit$er ~ toFit$eStd)
poly = polynomial(coef = fit$coefficients)
toPred = subset
colnames(toPred) = c(“actEr”,“eStd”)
toPred$er = predict(poly,toPred[,“eStd”])
toPred$diff = toPred$er – toPred$actEr
subset[i,“nAbove”] = nrow(toPred[which(toPred$diff > 0),])
}
# 得到切點
# 線以下是最大化
max = max(subset$nAbove)
er = subset[which(subset$nAbove == max),“er”]
eStd = subset[which(subset$nAbove == max),“eStd”]
# 市場投資組合的下標
idx = which(solution$er == er & solution$eStd == eStd)
# 畫線
if (graph){
maxStd = max(solution$eStd) + .02
maxRetg = max(solution$er) + .02
plot(solution$eStd,
solution$er,
xlim=c(0,maxStd),
ylim=c(0,maxRetg),
ylab=“Expected Yearly Return”,
xlab=“Expected Yearly Std Dev”,
main=“Efficient Frontier”,
col=“red”,
type=“l”,
lwd=2)
abline(v=c(0), col=“black”, lty=“dotted”)
abline(h=c(0), col =“black”, lty=“dotted”)
toFit = data.frame(er=rf,eStd=0)
toFit = rbind(toFit,solution[idx,c(“er”,“eStd”)])
fit = lm(toFit$er ~ toFit$eStd)
abline(coef=fit$coefficients,col=“blue”,lwd=2)
}
# 返回投資組合權重、eStd 和 eR
out = solution[idx,]
return (out)
}
例子
讓我們使用埃克森美孚(XOM),IBM(IBM),中期政府債券ETF(IEF)這個組合做測試。這裡假定你有importSeries()函式的定義。
require(polynom)
require(fImport)
require(PerformanceAnalytics)
require(tseries)
require(stats)
from = “2003-01-01″
to = “2011-12-16″
xom = importSeries(“xom”,from,to)
ibm = importSeries(“ibm”,from,to)
ief = importSeries(“ief”,from,to)
merged = merge(xom,ibm)
merged = merge(merged,ief)
vars = c(“xom.Return”,“ibm.Return”,“ief.Return”)
vars2 = c(“xom”,“ibm”,“ief”)
mp =
marketPortfolio(merged,.02,vars,vars2,graph=TRUE)
mp
日誌的輸出是:
建立的圖:
結論
這個投資組合優化的給了我們一個發現更低邊界例子。這是一個不正常的現象。
這就是為什麼我們結果子集只包括部分邊界的頂點(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){
# 初始化組合權重資料幀
weights = data.frame(t(rep(NA,length(weightNames))))
colnames(weights) = weightNames
weights = weights[-1,]
# 年化收益
t = table.AnnualizedReturns(merged[,returnNames])
# 優化範圍
maxRet = max(t['Annualized Return',]) - .005
minRet = min(t['Annualized Return',]) + .005
# 設定回報上下限
# 在.005%到50%之間
maxRet = min(.5,maxRet)
minRet = max(0.005,minRet)
# 如果都是預期回報均為負數,
# 那麼只返回收益最大的投資組合
if (maxRet < 0){
minRet = maxRet
points = 1
}
# 除錯列印
if (Debug){
print("Max Return")
print(maxRet)
print("Min Return")
print(minRet)
}
# portfolio.optim 不能處理含有 NA 值的時間序列,這裡過濾掉 NA 值
m2 = removeNA(merged[,returnNames])
er = NULL
eStd = NULL
# 在回報水平之間迴圈,尋找最優的投資組合
# portfolio.optim 使用日收益,因此我們必須作出相應調整
ok = FALSE
for (i in seq(minRet,maxRet,length.out=points)){
pm = 1+i
pm = log(pm)/255
# 除錯
if (Debug){
print("Finding Optimum for")
print("ER")
print(pm)
print(exp(pm*255)-1)
}
# 最優化 各組權重 <= 最大權重
# tryCatch 捕獲異常值
opt = tryCatch(portfolio.optim(m2,
pm = pm,
reshigh = rep(maxWeight,length(weightNames)),
shorts = FALSE),
error = function(err) return(NULL))
# 列印最優結果
if (Debug) print(opt)
# 檢查是否存在可行解
if (!is.null(opt)){
er = c(er, exp(pm*255) - 1)
eStd = c(eStd, opt$ps*sqrt(255))
w = t(opt$pw)
colnames(w) = weightNames
weights = rbind(weights,w)
# 更新變數
ok = (ok | TRUE)
} else {
print("ERROR IN THIS TRY")
# 更新變數
ok = (ok | FALSE)
}
}
# 如果在邊界沒有可行解,則返回NULL
if (!ok){
return (NULL)
}
solution = weights
solution$er = e
solution$eStd = eStd
# 找到最小標準差和最大平均收益點的索引值
minIdx = which(solution$eStd == min(solution$eStd))
maxIdx = which(solution$er == max(solution$er))
if (Debug){
print(minIdx)
print(maxIdx)
}
# 切分結果
subset = solution[minIdx:maxIdx,c("er","eStd")]
subset$nAbove = NA
# 對每個分片的值,計算在劃線下面的點的數量
for (i in seq(1,maxIdx-minIdx+1)){
toFit = data.frame(er=rf,eStd=0)
toFit = rbind(toFit,subset[i,c("er","eStd")])
fit = lm(toFit$er ~ toFit$eStd)
poly = polynomial(coef = fit$coefficients)
toPred = subset
colnames(toPred) = c("actEr","eStd")
toPred$er = predict(poly,toPred[,"eStd"])
toPred$diff = toPred$er - toPred$actE
subset[i,"nAbove"] = nrow(toPred[which(toPred$diff > 0),])
}
# 得到基準線以下的數量最大化的切點
max = max(subset$nAbove)
er = subset[which(subset$nAbove == max),"er"]
eStd = subset[which(subset$nAbove == max),"eStd"]
# 如果找到多個組合,返回第一個
if (length(er) > 1){
er = er[1]
eStd = eStd[1]
}
# 市場投資組合的索引
idx = which(solution$er == er & solution$eStd == eStd)
if (Debug){
print("solution")
print(er)
print(eStd)
print(solution[idx,])
}
# 如果需要可以加一條基準線
if (graph){
maxStd = max(solution$eStd) + .02
maxRetg = max(solution$er) + .02
plot(solution$eStd,
solution$er,
xlim=c(0,maxStd),
ylim=c(0,maxRetg),
ylab="Expected Yearly Return",
xlab="Expected Yearly Std Dev",
main="Efficient Frontier",
col="red",
type="l",
lwd=2)
abline(v=c(0), col="black", lty="dotted")
abline(h=c(0), col ="black", lty="dotted")
toFit = data.frame(er=rf,eStd=0)
toFit = rbind(toFit,solution[idx,c("er","eStd")])
fit = lm(toFit$er ~ toFit$eStd)
abline(coef=fit$coefficients,col="blue",lwd=2)
}
# 返回市場投資組合權重、eStd 、eR
out = solution[idx,]
return (out)
}
改進之處
我們的改進之處:
1.如果我們需要可以新增一個除錯選項列印輸出
2.增加容錯功能。有時直接得到一個可行解是不可能的,這個函式需要相應的檢測並且處理錯誤。
3.資本的最大回報我們限定在50%以下,這個值太大會導致其他奇怪的行為。
4.同樣,把最小收益的下界定在.005%。
5.如果最大收益是< 0,那麼簡單地找到最小方差投資組合。
6.新增一個maxWeight選項,讓我們限制每個證券標的的權重。
我們考慮的股票池現在是28個道瓊斯成分股(因為某些原因雅虎金融值提供了28只而不是30只)。我預先下載並存儲這些收益值為一個 .rda 檔案。你可以在這裡得到它。我計算每個股票的初始市值權重並存儲為 .csv 檔案。你可以在這裡得到它。
# 讀取已存的收益值load(“D:\Workspace\PortfolioOptimization\returns.rda”)
returns = results
rm(results)
stocks = colnames(returns)# 獲取大盤權重stockWgt = read.table(“D:\Workspace\PortfolioOptimization\stocks.csv”,
header=TRUE,sep=“,”)[,“Weight”]# 計算大盤權重投資組合的回報results = as.matrix(returns) %*% stockWgt
colnames(results) = c(“CapWeight Portfolio”)
results = as.timeSeries(results)# 計算等權重投資組合的回報ret = t(as.matrix(rep(1/length(stocks),length(stocks))))
resEqual = as.matrix(returns) %*% t(ret)
colnames(resEqual) = “EqualWeight Portfolio”# 彙總結果到一個時間序列物件中results = cbind(results,resEqual)# 獲取收益值的日期序列dates = time(returns)
dates = dates@Data# 從日期計算季度qtrs = quarters(dates)
qtrs = cbind(dates,qtrs)
keep = NULL
lastQtr = “n”#遍歷日期和季度序列,只保留每個季度第一天的日期for (i in seq(1,nrow(qtrs))){ if (qtrs[i,2] == lastQtr){ if (i == nrow(qtrs)){
keep = c(keep,1)
} else {
keep = c(keep,0)
}
}else {
keep = c(keep,1)
}
lastQtr = qtrs[i,2]
}
qtrs = cbind(qtrs,keep)# 獲取每個季度第一天的下標indx = which(qtrs[,3] == 1)# 對每個週期的第一個季度,使用等權策略res = as.matrix(returns[indx[1]:(indx[2]–1),]) %*% t(ret)#對每個週期基於上一季度的資料進行迴圈計算大盤組合的表現for (i in seq(2,length(indx)–1)){ print(“Running “) print(i)
# 得到上季度股票回報的子集
subset = returns[indx[i–1]:(indx[i]–1),] s = start(subset)
e = end(subset) print(“Fitting for:”) print(s) print(e)
# 計算大盤投資組合
mp = marketPortfolio(subset,.02,stocks,stocks,
graph=TRUE,
points=500,
Debug=FALSE)
#如果優化失敗,使用等權策略
if (is.null(mp)){
ret = t(as.matrix(rep(1/length(stocks),length(stocks))))
} else {
ret = as.matrix(mp[,stocks])
}
# 本季度的子集
subRes = returns[indx[i]:(indx[i+1]–1),] s = start(subRes)
e = end(subRes) print(“Calculating Returns for:”) print(s) print(e)
# 計算當前季度的大盤策略的收益並追加在收益序列後面
subRes = as.matrix(subRes) %*% t(ret)
res = rbind(res,subRes)
}# 迴圈計算時,序列的最後一天不計算收益subRes = returns[nrow(returns),]
subRes = as.matrix(subRes) %*% t(ret)
res = rbind(res,subRes)# 新增組合優化策略colnames(res) = “Portfolio Optimization”
res = as.timeSeries(res)
results = cbind(results,res)#計算年化收益統計特徵table.AnnualizedReturns(results)#計算並繪製相關性png(“d:\Workspace\PortfolioOptimization\mpCorr.png”)
chart.Correlation(results,histogram=TRUE,pch=“+”)
dev.off();##計算並繪製累積收益png(“d:\Workspace\PortfolioOptimization\mpReturns.png”)
chart.CumReturns(results,
main=“Total Returns CapWeight vs PortOpt”,
legend.loc=“topleft”)
dev.off()
我們的年回報率表:
結論
我們的投資組合優化策略優於大盤權重策略,但跑輸了等權重策略。如果你支援阿諾特的話就覺得這沒什麼奇怪的了,這只是因為我們沒有打破價格的相關性罷了。
這是相關性的圖表:
我們已經建立了一個和大盤權重策略非常相關的策略,但是還是不如等權策略。等權策略和大盤權重策略的關聯度是非常有趣的。
這是收益繪製的時間序列:
有趣的是,可以看到在圖中綠色的部分顯示我們的投資組合在2009年3月份的市場底部開始有一個快速反彈。這大大跑贏了大盤權重組合。
本文連結:https://segmentfault.com/a/1190000004430109?from=timeline&isappinstalled=1
end