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

概述

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

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

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

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

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

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

第一部分

數字特徵計算

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

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

pacman::p_load(
  fImport,
  PerformanceAnalytics,
  stringb,
  tidyverse)

# 將股票數據加載到一個時間序列對象的函數
importSeries = function(symbol,from,to) {

    # 從雅虎讀取金融數據
    input = yahooSeries(symbol,
                        from = from,
                        to = to)

    # 列名
    adjClose = symbol %.% ".Adj.Close"
    inputReturn = symbol %.% ".Return"
    CReturn = symbol %.% ".CReturn"

    # 計算收益率並生成時間序列
    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) %>% merge(ief)

vars = c("tlt.Return",
         "shy.Return",
         "ief.Return")
# 計算年回報率
(t = table.AnnualizedReturns(merged[,vars],
                            Rf = mean(merged[,"shy.Return"],
                                      na.rm=TRUE)))

結果以下:ui

標的 年化收益率 年化波動率 年化夏普率 (Rf=2.81%)
tlt.Return 0.0772 0.0283 0.0645
shy.Return 0.1404 0.0173 0.0740
ief.Return 0.3378 -0.0086 0.4729

槓鈴策略

若是你常常看娛樂投資電視臺,你最終會聽到"槓鈴策略"這個術語。這是指一個極端的投資組合分配方案。全部的權重都是極端狀況,你能夠想象這是一個槓鈴。在政府債券的投資組合,這將意味着購買期限長或短而不是持有中間。那麼什麼樣的風險收益狀況下你會採用這個策略?

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

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

$$
R_p=W_{TLT}R_{TLT}+W_{SHY}R_{SHY}
$$
$$
s.t. W_{TLT}+W_{SHY}=1
$$

顯然這兩種資產具備相關性(在馬科維茨於1952年的博士論文發表以前,投資經理不瞭解相關性而且默認假設爲1 -馬科維茨所以得到了諾貝爾獎)。假設回報是正態分佈的,那麼投資組合方差將是:

$$
V_p=W_{TLT}^2σ_{TLT}^2+W_{SHY}^2σ_{SHY}^2+W_{TLT}W_{SHY}σ_{TLT}σ_{SHY}Corrleft(TLT,SHYright)
$$

$$
s.t. W_{TLT}+W_{SHY}=1
$$

風控模型

咱們能夠根據這兩個部分的知識改變持倉權重併爲咱們的槓鈴策略創建風險收益模型。

# 檢查相關性
corr = cor(merged[,vars],
           use = "complete.obs")

c = corr["tlt.Return","shy.Return"]
# 假設一個槓鈴策略是持有長期和短時間資產
# 定義風險、收益
ws = NULL
wt = NULL
mu = NULL
sigma = NULL
# 50個觀察
n=50
# 遍歷槓鈴策略的權重

rTLT = t["Annualized Return","tlt.Return"]
rSHY = t["Annualized Return","shy.Return"]
sTLT = t["Annualized Std Dev","tlt.Return"]
sSHY = t["Annualized Std Dev","shy.Return"]

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)
}
# 風險收益的數據集
rrProfile = data.frame(ws=ws,wt=wt,mu=mu,sigma=sigma)

注意,上面的方程是二次的。咱們能夠配合咱們剛剛建立的點畫出拋物線。注意,而習慣上把風險放在X軸上,而把擬合方差(風險)做爲因變量放在Y軸。

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

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

# 獲得迴歸係數
coe = fit$coefficients
# 獲得每一個迴歸預測的風險值
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(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")
# 畫出預測邊值
lines(sfit,muf,col="red")

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

# 如今讓咱們添加第三個標的。
# 除非咱們想作一個格點搜索,不然咱們須要對每一個級別的回報減小風險來優化投資組合。
# portfolio.optim 在時間序列中不能有 NA 值。

m2 = removeNA(merged[,vars])
wSHY = NULL
wIEF = NULL
wTLT = NULL
er = NULL
eStd = NULL

# 在回報水平之間不斷循環搜索找到最優的投資組合,包括最小值(rSHY)和最大值(rTLT)
# portfolio.optim 使用日回報數據,所以咱們不得不作出相應的調整

for (i in seq((rSHY+.001),(rTLT-.001),length.out=100)){
      pm = 1+i
      pm = log(pm)/255
      opt = tseries::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(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")
# 畫出預測邊值
lines(sfit,muf,col="red")
# 畫出三個標的的有效邊界。

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)

結論

以下圖:

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

相關代碼

第二部分

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

無風險利率假定

若是你不肯意看維基百科,我也會解釋相關概念的。若是你有一個保底回報率(無風險利率),那麼資產位於圖表的y軸。在邊界的切點處畫一條切線,切點表明着很是有效的投資組合。你能夠混合持有必定權重的組合標的和無風險資產,實現比邊界曲線更好的風險回報比。

明白了嗎?很是棒!

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

多項式擬合

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

做爲一個例子,這裏雖然六階多項式的擬合符合缺陷,但咱們的切線點不是有用的。

只有一個實根,其他的都是虛根,咱們須要另外一種方法。

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

第三部分

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

放鬆約束

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

$$CML_i<=EF_i$$

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

$$Portfolio_j\ that\ max\ Count(CML_i < EF_i)$$

我整理如下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 in seq(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()函數的定義。

pacman::p_load(
  polynom,
  fImport,
  PerformanceAnalytics,
  tseries,
  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))

日誌的輸出是:

xom ibm ief er eStd
0.09395 0.1378 0.7682 0.07762 0.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){
# 初始化組合權重數據幀
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 = er
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$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"]
# 若是找到多個組合,返回第一個
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 文件。你能夠在這裏獲得它

# 讀取已存的收益值
download.file("http://www.pazzula.com/blog/returns.rda","returns.rda")
load("returns.rda")
returns = results
rm(results)
stocks = colnames(returns)
# 獲取大盤權重
stockWgt = read.table("http://www.pazzula.com/blog/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("mpCorr.png")
chart.Correlation(results,histogram=TRUE,pch="+")
dev.off();
##計算並繪製累積收益
png("mpReturns.png")
chart.CumReturns(results,
            main="Total Returns CapWeight vs PortOpt",
            legend.loc="topleft")
dev.off()

咱們的年回報率表:

Result CapWeight Portfolio EqualWeight Portfolio Portfolio Optimization
Annualized Return -0.0393 0.0128 0.0069
Annualized Std Dev 0.2530 0.2242 0.1785
Annualized Sharpe (Rf=0%) -0.1554 0.0570 0.0387

結論

咱們的投資組合優化策略優於大盤權重策略,但跑輸了等權重策略。若是你支持阿諾特的話就以爲這沒什麼奇怪的了,這只是由於咱們沒有打破價格的相關性罷了。

這是相關性的圖表:

咱們已經建立了一個和大盤權重策略很是相關的策略,可是仍是不如等權策略。等權策略和大盤權重策略的關聯度是很是有趣的。

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

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

做爲分享主義者(sharism),本人全部互聯網發佈的圖文均聽從CC版權,轉載請保留做者信息並註明做者 Harry Zhu 的 FinanceR專欄:https://segmentfault.com/blog...,若是涉及源代碼請註明GitHub地址:https://github.com/harryprince。微信號: harryzhustudio商業使用請聯繫做者。

相關文章
相關標籤/搜索