これまでにモメンタム戦略を使った株式の分析を幾つかの記事で紹介してきました。
私が使った解析コードを公開するので、Rが使える方はぜひ戦略を練ったり、モメンタム戦略の穴を探したりするのに役立ててみてください。
※2019/1/29加筆:コードを一部修正しました。ウェイト計算用のlook backしたリターンの日付が間違っていたのを修正しました。ご指摘いただいたhassさんありがとうございます。
パッケージ、設定
パッケージは3つのみ使いました。
####パッケージ読み込み#### library(Quandl) library(quantmod) library(PerformanceAnalytics) Quandl.api_key("*******")
自分で関数を幾つか定義して使用しています。
#### Function読み込み #### #### データセット編集に使用#### # return_list(asset_list, period) # 株価/ポートフォリオのXTSデータのリストからまとめてリターンを計算する # period = "monthly", "quarterly", "yearly"など return_list <- function(asset_list, period){ data_list <- as.list(NA) for (i in 1:length(asset_list)) { temp <- periodReturn(asset_list[[i]], period = period, subset=paste(start, "::", sep = ""), type = 'arithmetic', indexAt = 'lastof') colnames(temp) <- names(asset_list[i]) data_list[[i]] <- temp } data_list <- Reduce(cbind,lapply(data_list, merge.xts)) if(any(is.na(data_list)) == TRUE) { warning(paste("NAを0.0で埋めます")) data_list <- na.fill(data_list, fill = 0) } return(data_list) } # return_period_adjust(x, period) # return_listで作ったリターンのリスト(x)の時系列の単位を変更する # デフォルトは四半期("quarters")、他にも月足("months")、年("years") etc... return_period_adjust <- function(data_list, period = 'quarters'){ ep <- endpoints(data_list, period) for (i in 1:ncol(data_list)){ if(i==1){ x <- period.apply(data_list[,i]+1, INDEX=ep, prod)-1 }else{ temp <- period.apply(data_list[,i]+1, INDEX=ep, prod)-1 x <- merge.xts(x, temp) } } return(x) } # check_return(data_list) # return_listで作ったリターンのリスト(data_list)の各セルを任意期間のリターンに変換する # デフォルトはdays=365: 直近12ヶ月のリターンを計算する check_return <- function(data_list, days=365){ temp <- data_list * 0 for(i in 1:nrow(data_list)){ dateto <- index(data_list[i]) datefrom <- as.character(as.Date(dateto)-days+1) fromdateto <- c(paste(datefrom,"::",dateto,sep="")) temp[i,1:ncol(temp)] <- apply(data_list[fromdateto]+1,2,prod)-1 } index(temp) <- index(temp) + 1 return(temp) } # rebalance_cost(w, cost) # ウェイトを調整してリバランス時の手数料を再現する # x:ウェイト(XTS形式)、cost:リバランス手数料(%) rebalance_cost <- function(w, cost=0.5){ temp <- w temp <- apply(diff(temp) * sign(diff(temp)),1,sum) temp <- na.fill(temp, 0) w <- w * (1 - temp * cost * 0.01) return(w) } #### モメンタムの戦略を再現する関数 #### # momentumfundamental(CheckReeturn) # CheckReturn:リバランスの指標にするリスト # ・リバランスのタイミングで、 # ・過去12ヶ月に成績が良かったアセットクラス(ポートフォリオ)のみを100%ホールドする # checkreturn:リターンのリスト # ウェイト(XTS)が出力される momentumfundamental <- function(CheckReturn){ temp <- as.data.frame(CheckReturn) return_check_rank <- t(apply(temp, 1,rank)) w <- t(apply(return_check_rank, 1, function(x){ifelse(x == max(x), 1, 0)})) w <- t(apply(w, 1, function(x){return(x/sum(x))})) w <- as.xts(w) return(w) } # hold_or_sell(CheckReturn, w, benchmark) # CheckReturn:リバランスの指標にするリスト, w:基本となるウェイト, benchmark:リターンの基準にするアセットアセットの名前(character) # 設定したウェイトで開始 # リバランスのタイミングで、 # 昨年度の成績がbenchmarkより良ければホールド # 悪ければbenchmarkに移動する # ウェイト(xts)が出力 hold_or_sell <- function(CheckReturn, w, benchmark){ temp<- CheckReturn for(i in 1:nrow(temp)){ for(j in 1:ncol(temp)){ temp[i,j] <- ifelse(CheckReturn[i,j] >= CheckReturn[i,benchmark], w[j], 0) } } temp[,benchmark] <- 1-t(apply(temp, 1, sum)) #short-term billsのweightを計算 temp <- as.xts(temp) return(temp) } # regulate_weight(CheckReturn, w, benchmark) # CheckReturn:リバランスの指標にするリスト, w:基本となるウェイト, benchmark:リターンの基準にするアセットアセットの名前(character) # 設定したポートフォリオで開始 # リバランスのタイミングで # 昨年度の成績がベンチマークより良ければホールド、悪ければ0 # 残金はホールドする資産の割合で振り分ける(株、長期債なら(30%,40%)→(43%,57%)のポートフォリオになる) # ホールドすべき資産が無ければベンチマークで持つ # ウェイト(xts)が出力 regulate_weight <- function(CheckReturn, w, benchmark){ temp <- CheckReturn for(i in 1:nrow(temp)){ for(j in 1:ncol(temp)){ temp[i,j] <- ifelse(CheckReturn[i,j] >= CheckReturn[i, benchmark], w[j], 0) } } check_sum <- apply(temp, 1, sum) for(i in 1:length(check_sum)){ if (check_sum[i] > 0) { temp[i,] <- temp[i,] * 1/check_sum[i] } else { temp[i,benchmark] <- 1 } } temp <- as.xts(temp) return(temp) }
定義した関数
return_list(asset_list, period) :
quantmodなどで読み込んだxtsファイルを格納したリストを入力して、periodで指定した単位でのリターンを求める関数です。
asset_list : priceを表すquanntmodなどで読み込んだxtsのリスト
period : “monthly”, “quarterly”, “yearly”などを指定
リターンを格納したリストを出力します。
return_period_adjust(x, period)
リターンを格納したリストの時系列の単位を変更します。日足のリターンから月足のリターン、四半期のリターンを計算したりできます。
x: リターンを格納したxts形式のリスト(return_listの出力と同じ)
period: デフォルトは四半期(“quarters”)、他にも月足(“months”)、年(“years”) etc..
check_return(data_list, days=365)
return_listで作ったリターンのリスト(data_list)の各セルを任意期間(days)のリターンに変換する
モメンタム戦略でポートフォリオのウェイトを変える時の参考にするための12ヶ月リターンを計算するのに使います。
例えば、ポートフォリオに組み込むアセットの四半期リターンのリストの各セルには3ヶ月分のリターンが格納されていますが、そこから各セルに12ヶ月分のリターンを返します。
data_list : return_listの出力で得られたリターンのリストを使用
days : デフォルトは365日(12ヶ月)、182日にすれば半年分のリターンが結果として返されます。
rebalance_cost(w, cost)
モメンタム戦略でウェイトを変えながら投資をする時に発生する売買に伴う手数料による減価を再現します。
w: ウェイトを表すxtsを入力
cost: リバランスの手数料(%)を入力します。デフォルトは片道0.5%としています。
モメンタム戦略を再現する関数
モメンタム戦略で重要な、ポートフォリオの中身(ウェイト)を変えるのに使います。
check_returnでもとめた、各チェックポイントでの12ヶ月リターンを示すxts(CheckReturn)を入力すると調整したウェイトを出力します。
売買コストを計算するときは出力されたxtsをrebalance_costで処理します
momentumfundamental(CheckReturn)
CheckReturn: check_returnの出力で得られた12ヶ月リターンのxts
・リバランスのタイミングで、
・過去12ヶ月に成績が良かったアセットクラス(ポートフォリオ)のみを100%ホールドする
ウェイト(XTS)が出力される
hold_or_sell(CheckReturn, w, benchmark)
CheckReturn: check_returnの出力で得られた12ヶ月リターンのxts
w:基本となるウェイト(オールウェザーポートフォリオをベースにするならオールウェザーを示すvector)
benchmark:リターンの基準にするアセットアセットの名前(character)
・設定したウェイトで開始
・リバランスのタイミングで、
・昨年度の成績がbenchmarkより良ければホールド
・悪ければbenchmarkに移動する
ウェイト(xts)が出力
regulate_weight(CheckReturn, w, benchmark)
・CheckReturn: check_returnの出力で得られた12ヶ月リターンのxts
w:基本となるウェイト
benchmark:リターンの基準にするアセットアセットの名前(character)
・設定したポートフォリオで開始
・リバランスのタイミングで
・昨年度の成績がベンチマークより良ければホールド、悪ければ0
・残金はホールドする資産の割合で振り分ける(株、長期債なら(30%,40%)→(43%,57%)のポートフォリオになる)
・ホールドすべき資産が無ければベンチマークで持つ
ウェイト(xts)が出力
使い方
・quantmodなどで読み込んで、xtsのリストを作る
・return_listでxtsを処理してリターンをまとめて求める
・必要があればreturn_period_adjustで期間を調節(月足→四半期足とか)
・求めたリターンから、モメンタムの指標をcheck_returnで作る
・モメンタムの関数を使って時系列のウェイトを作る
・rebalance_costでコストを計算。
・performance analyticsでポートフォリオのリターンを計算する
です。実際にやってみましょう。
設定の読み込み
レバレッジなしのものを紹介します。レバ有りはソースだけ一番下に貼ります。
####期間の設定#### start = "1987-12-31" end = "2018-03-31" ####データ読み込み#### sp500 <- getSymbols("^GSPC", src = 'yahoo', auto.assign = FALSE, warnings = FALSE, from=start, to=end) vustx <- getSymbols("VUSTX", src = 'yahoo', auto.assign = FALSE, warnings = FALSE, from=start, to=end) vbmfx <- getSymbols("VBMFX", src = 'yahoo', auto.assign = FALSE, warnings = FALSE, from=start, to=end) gold <- Quandl("WGC/GOLD_DAILY_USD", type="xts", start_date=start, end_date=end) commodity <- Quandl("FRED/PPIACO", type="xts", start_date=start, end_date=end) bills <- getSymbols("VSGBX", src = 'yahoo', auto.assign = FALSE, warnings = FALSE, from=start, to=end) # mutual fundのリターンを設定 vbmfx_edit <- vbmfx[,6] bills_edit <- bills[,6] vustx_edit <- vustx[,6] asset_list <- list(sp500 = sp500, vustx = vustx_edit, vbmfx = vbmfx_edit, gold = gold, commodity = commodity, bills = bills_edit) #return_listに入力するxtsのリスト ####シミュレーションの前準備#### merged_monthly_returns <- return_list(asset_list, 'monthly') #月ごとのリターンのリスト merged_quarterly_returns <- return_list(asset_list, 'quarterly') #四半期ごとのリターンのリスト quarterly_return_check <- check_return(merged_quarterly_returns) #四半期ごとに見直す12ヶ月分のリターンのリスト
・月ごとのリターン(merged_monthly_returns)
・四半期ごとのリターン(merged_quarterly_returns)
・モメンタム戦略の指標にする3ヶ月おきに計算した12ヶ月のリターン(quarterly_return_check)
が得られました。
モメンタム戦略を検証する
# オールウェザーポートフォリオ w_all_weather <- c(.3, .4, .15, .075, .075, .0) #ウェイト all_weather <- Return.portfolio(merged_monthly_returns, weights = w_all_weather, rebalance_on = "quarters", geometric = TRUE) charts.PerformanceSummary(all_weather, main='All-weather portfolio', ylog = TRUE) # S&P500バイ・アンド・ホールド w_sp500 <- c(1.0, .0, .0, .0, .0, .0) #ウェイト sp500only <- Return.portfolio(merged_monthly_returns, weights = w_sp500, rebalance_on = "quarters", geometric = TRUE) charts.PerformanceSummary(sp500only, main='S&P Buy and Hold', ylog = TRUE)
紹介した戦略は以下のようにしてシミュレーションしました。
# 戦略1 Momentum fundamentalism w1 <- momentumfundamental(quarterly_return_check) #リバランスで変更するウェイトを計算 w1 <- rebalance_cost(w1, cost = 0.0) s1 <- Return.portfolio(merged_monthly_returns, weights=w1, rebalance_on = "quarters", geometric = TRUE) strategy1 <- merge(s1, all_weather, sp500only) names(strategy1) <- c("Momentum fundamentalism", "All-weather", "S&P500") charts.PerformanceSummary(strategy1, main='Moment fundamentalism', ylog = TRUE) # 戦略2 Weather report w_all_weather <- c(.3, .4, .15, .075, .075, .0) w2 <- hold_or_sell(quarterly_return_check, w_all_weather, "bills") w2 <- rebalance_cost(w2, cost = 0.0) s2 <- Return.portfolio(merged_monthly_returns, weights=w2, rebalance_on = "quarters", geometric = TRUE) strategy2 <- merge(s2, all_weather, sp500only) names(strategy2) <- c("Weather Report", "All-weather","S&P500") charts.PerformanceSummary(strategy2, main='Weather Report') # 戦略3 Weather report ver 2 w_all_weather <- c(.3, .4, .15, .075, .075, .0) w3 <- regulate_weight(quarterly_return_check, w_all_weather, "bills") w3 <- rebalance_cost(w3, cost = 0.0) s3 <- Return.portfolio(merged_monthly_returns, weights=w3, rebalance_on = "quarters", geometric = TRUE) strategy3 <- merge(s3, all_weather, sp500only) names(strategy3) <- c("Weather Report ver2", "All-weather", "S&P500") charts.PerformanceSummary(strategy3, main='Weather Report 2') # 戦略4 hiroakit_normal #それぞれのポートフォリオのリターンをまず求める→リストにして結合 w4_bull <- c(.6,.2,.2,.0,.0,.0) w4_mid <- c(.3,.15,.15,.2,.2,.0) w4_bear <- c(.3,.35,.35,.0,.0,.0) compare1 <- Return.portfolio(merged_monthly_returns, weights=w4_bull, rebalance_on = "quarters", geometric = TRUE) compare2 <- Return.portfolio(merged_monthly_returns, weights=w4_mid, rebalance_on = "quarters", geometric = TRUE) compare3 <- Return.portfolio(merged_monthly_returns, weights=w4_bear, rebalance_on = "quarters", geometric = TRUE) compare_portfolio <- merge(compare1,compare2, compare3) #ポートフォリオ毎のリターンをリストにしてモメンタム戦略のウェイトを求める w4_portfolio_return <- check_return(compare_portfolio) w4 <- momentumfundamental(w4_portfolio_return) w4 <- rebalance_cost(w4, cost = 0.0) s4 <- Return.portfolio(compare_portfolio, weights=w4, rebalance_on = "quarters", geometric = TRUE) strategy4 <- merge(s4, compare1, compare2, compare3) names(strategy4) <- c("@hiroakit_roko", "Bull", "Mid", "Bear") charts.PerformanceSummary(strategy4, main='@hiroakit_roko', ylog=TRUE) #上の戦略をまとめてチャートにする strategies <- merge(sp500only, all_weather, s1, s2, s3, s4) names(strategies) <- c("S&P500", "All weather", "Momentum fundamentalism", "Weather report", "Weather report ver2", "@hiroakit_normal") charts.PerformanceSummary(strategies, main='Momentum strategies', ylog=TRUE)
1〜3までは各戦略の関数を使っただけでほぼおなじです。4ではポートフォリオを設定してそれぞれのリターンを計算した上で、さらにそこからモメンタム戦略につかうリターンの比較を行っています。参考にしてみてください。
デュアルモメンタムをするときは、幾つかのETF+ベンチマークにするETFのリターンをmomentumfundamentalにかける→ポートフォリオが出来る。それで作ったポートフォリオをさらに組み合わせる、などでポートフォリオを設定できると思います。
####期間の設定#### start = "1987-12-31" end = "2018-03-31" ####データ読み込み#### sp500 <- getSymbols("^GSPC", src = 'yahoo', auto.assign = FALSE, warnings = FALSE, from=start, to=end) vustx <- getSymbols("VUSTX", src = 'yahoo', auto.assign = FALSE, warnings = FALSE, from=start, to=end) vbmfx <- getSymbols("VBMFX", src = 'yahoo', auto.assign = FALSE, warnings = FALSE, from=start, to=end) gold <- Quandl("WGC/GOLD_DAILY_USD", type="xts", start_date=start, end_date=end) commodity <- Quandl("FRED/PPIACO", type="xts", start_date=start, end_date=end) bills <- getSymbols("VSGBX", src = 'yahoo', auto.assign = FALSE, warnings = FALSE, from=start, to=end) # mutual fundのリターンを設定 vbmfx_edit <- vbmfx[,6] bills_edit <- bills[,6] vustx_edit <- vustx[,6] asset_list <- list(sp500 = sp500, vustx = vustx_edit, vbmfx = vbmfx_edit, gold = gold, commodity = commodity, bills = bills_edit) merged_monthly_returns <- return_list(asset_list, 'monthly') #月ごとのリターンのリスト #レバレッジETFのリターンをシミュレート lev_monthly_returns <- merged_monthly_returns[,c(1,2)] * c(3.0, 3.4) colnames(lev_monthly_returns) <- c("spxl", "tmf") #レバレッジETFと合わせてリターンのリスト、リバランス用のリターンのリストを作成 merged_monthly_returns <- merge.xts(merged_monthly_returns, lev_monthly_returns) merged_quarterly_returns <- return_period_adjust(merged_monthly_returns, period = "quarters") quarterly_return_check <- check_return(merged_quarterly_returns) #四半期ごとに見直す12ヶ月分のリターンのリスト ####ポートフォリオ(比較用)#### # レバレッジドオールウェザーポートフォリオ w_lev_all_weather <- c(.0, .0, .25, .15, .15, .0, .20, .25) lev_all_weather <- Return.portfolio(merged_monthly_returns, weights = w_lev_all_weather, rebalance_on = "quarters", geometric = TRUE) charts.PerformanceSummary(lev_all_weather, main='Leveraged All-weather portfolio', ylog = TRUE) # レバレッジドPF リスク大 w_levpf <- c(.0, .0, .3, .0, .0, .0, .4, .3) levpf_high <- Return.portfolio(merged_monthly_returns, weights = w_levpf, rebalance_on = "quarters", geometric = TRUE) charts.PerformanceSummary(levpf_high, main='Leveraged PF high', ylog = TRUE) ####モメンタム戦略の検証#### # 戦略2-1 Leveraged weather report w_lev_all_weather <- c(.0, .0, .25, .15, .15, .0, .2, .25) #レバレッジドオールウェザーポートフォリオのウェイト w21 <- hold_or_sell(quarterly_return_check, w_lev_all_weather, "bills") w21 <- rebalance_cost(w21, cost = 0.0) s21 <- Return.portfolio(merged_monthly_returns, weights=w21, rebalance_on = "years", geometric = TRUE) strategy21 <- merge(s21, lev_all_weather, sp500only) names(strategy21) <- c("Leveraged Weather Report", "Leveraged All-weather","S&P500") charts.PerformanceSummary(strategy21, main='Leveraged Weather Report', ylog = TRUE) # 戦略2-2 Leveraged PF momentum w_levpf <- c(.0, .0, .3, .0, .0, .0, .4, .3) w22 <- hold_or_sell(quarterly_return_check, w_levpf, 'gold') w22 <- rebalance_cost(w22, cost = 0.5) s22 <- Return.portfolio(merged_monthly_returns, weights=w22, rebalance_on = "quarters", geometric = TRUE) strategy22 <- merge(s22, lev_all_weather, levpf_high) names(strategy22) <- c("Momentum leveraged PF", "Leveraged All-weather", "Leveraged PF High") charts.PerformanceSummary(strategy22, main='Momentum Leveraged PF', ylog = TRUE) # 戦略2-3 hiroakit_lev w23_bull <- c(.0 , .0, .3, .0, .0 ,.0, .4, .3) w23_mid <- c(.3, .15, .15, .2, .2, .0, .0, .0) w23_bear <- c(.3, .35, .35, .0, .0, .0, .0, .0) compare1 <- Return.portfolio(merged_monthly_returns, weights=w23_bull, rebalance_on = "quarters", geometric = TRUE) colnames(compare1) <- "bull" compare2 <- Return.portfolio(merged_monthly_returns, weights=w23_mid, rebalance_on = "quarters", geometric = TRUE) colnames(compare2) <- "mid" compare3 <- Return.portfolio(merged_monthly_returns, weights=w23_bear, rebalance_on = "quarters", geometric = TRUE) colnames(compare3) <- "bear" compare_portfolio <- merge(compare1,compare2, compare3) w23_quarterly_return <- return_period_adjust(compare_portfolio, period='quarters') #月々のリターン→四半期のリターンに変換 w23_return_check <- check_return(w23_quarterly_return) w23 <- momentumfundamental(w23_return_check) w23 <- rebalance_cost(w23, cost = 0.0) s23 <- Return.portfolio(compare_portfolio, weights=w23, rebalance_on = "quarters", geometric = TRUE) strategy23 <- merge(s23, compare1, compare2, compare3) names(strategy23) <- c("@hiroakit_roko_2", "Bull", "Mid", "Bear") charts.PerformanceSummary(strategy23, main='Leveraged @hiroakit_roko', ylog=TRUE) #戦略2-4 hiroakit_lev_2 w24_bull <- c(.0, .0, .25, .0, .0, .0, .45, .3) w24_bear <- c(.3, .4, .15, .075, .075, .0, .0, .0) compare1 <- Return.portfolio(merged_monthly_returns, weights=w24_bull, rebalance_on = "quarters", geometric = TRUE) colnames(compare1) <- "bull" compare2 <- Return.portfolio(merged_monthly_returns, weights=w24_bear, rebalance_on = "quarters", geometric = TRUE) colnames(compare2) <- "bear" compare_portfolio <- merge(compare1,compare2) w24_quarterly_return <- return_period_adjust(compare_portfolio, period='quarters') w24_return_check <- check_return(w24_quarterly_return) w24 <- momentumfundamental(w24_return_check) w24 <- rebalance_cost(w24, cost = 0.0) s24 <- Return.portfolio(compare_portfolio, weights=w24, rebalance_on = "quarters", geometric = TRUE) strategy24 <- merge(s24, compare1, compare2) names(strategy24) <- c("@hiroakit_roko_3", "Bull", "Bear") charts.PerformanceSummary(strategy24, main='Leveraged @hiroakit_roko', ylog=TRUE) #上の戦略をまとめてチャートにする strategies <- merge(lev_all_weather, levpf_high, s21, s22, s23, s24) names(strategies) <- c("Leveraged all weather", "Leveraged PF High", "Leveraged weather report", "Leveraged PF momentum", "@hiroakit_lev1", "@hiroakit_lev2") charts.PerformanceSummary(strategies, main='Leveraged momentum strategies', ylog=TRUE)
コメント
いつもお世話になっております.hassです.
最近モメンタムで遊んでみようかと思い,カーネルさんのコードを参考にさせてもらっています.
すこし疑問に思う箇所があったのでコメントします.
過去の成績からウェイトを決める際に,未来の情報を使ってしまっているように見えます.
例えば,毎月リバランスをする際に,月末時点のモメンタム1位を選んで,その翌月の初めに入れ替えるという運用になると思います.しかし,momentumfundamentalとかの出力を確認すると,翌月のウェイトが当月末のモメンタム1位ではなく,翌月のモメンタム1位で選ばれているようにみえます.
ご確認ください.
hassさん、コメントありがとうございます。
関数がおかしかったのは確かにそうで、その後修正して検証しなおしたりしていました。ブログ記事の方は修正していなかったかもしれません。時間がある時に直してみます。ご指摘ありがとうございます。