ネット証券で、ETF(上場投資信託)の取引手数料の無料化が拡大しています。ETF投資をするのに、国内株式・海外株式・国内債券・外国債券など、どの資産に投資するか資産配分問題があります。この資産配分を自動でやってくれるのが、WealthNaviやTHEOなどのロボットアドバイザー投資です。
ロボットアドバイザー投資の代表的なサービスがWealth Naviで、仕組みを公開してくれています。
参考 WealthNaviの資産運用アルゴリズムWealthNavi
この2019年4月時点のデータを使って、PythonのPyPortfolioOptライブラリで、資産配分最適化したポートフォリオを作成してみました。
データ作成
# パッケージ読み込み
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
from pypfopt.efficient_frontier import EfficientFrontier
%matplotlib inline
7資産(米国株・日欧株・新興国株・米国債券・物価連動債・金・不動産)のリスク、期待リターン、相関行列のデータ(2019年4月時点、米ドルベース)です。
# データ設定(2019年4月時点)
assetName = ['Stock_US', 'Stock_Japan', 'Stock_EM',
'Bond_US', 'Bond_PL', 'Gold', 'Estate']
risk = [0.129, 0.148, 0.181, 0.029, 0.044, 0.157, 0.166]
r = [0.072, 0.081, 0.088, 0.026, 0.028, 0.037, 0.068]
cor = np.array([[1, 0.9, 0.7, -0.1, 0.1, 0.0, 0.7],
[0.9, 1, 0.8, 0, 0.2, 0.1, 0.6],
[0.7, 0.8, 1, 0.1, 0.3, 0.3, 0.5],
[-0.1, 0, 0.1, 1, 0.8, 0.4, 0.3],
[0.1, 0.2, 0.3, 0.8, 1, 0.5, 0.3],
[0, 0.1, 0.3, 0.4, 0.5, 1, 0.1],
[0.7, 0.6, 0.5, 0.3, 0.3, 0.1, 1]])
リスクの対角行列を作成し、行列演算を使って、相関行列から共分散行列を作成します。
# 共分散行列計算
riskDiag = np.diag(risk)
cov = np.dot(np.dot(riskDiag, cor), riskDiag)
df = pd.DataFrame(cov, index = assetName, columns=assetName)
データ可視化
リスク・リターンの散布図を描きます。
for (i,j,k) in zip(risk,r,assetName):
plt.scatter(i,j)
plt.annotate(k, xy=(i, j))
plt.style.use('ggplot')
plt.xlabel("risk")
plt.ylabel("expected return")
plt.show()

金と不動産が他の資産に比べて、リスクに対して期待リターンが小さく、資産配分比率が小さくなることが予想されます。他資産と相関が低くリスク低減効果があるか相関行列をみてみます。
#相関行列ヒートマップ
sns.heatmap(cor, annot=True, xticklabels=assetName,
yticklabels=assetName, cmap='Blues')

金は、他資産と相対的に相関が低そうです。また、不動産は、株式資産クラス内では相対的には相関は低そうです。
ポートフォリオ最適化
上記データをインプットとして、どの資産に配分すべきかポートフォリオの最適化をします。WealthNaviでは、投資家のリスク許容度を考慮した期待リターン最大化問題として定式化していますが、ここでは、最も基本的なリスク最小化として解いています。
PyPortfolioOptというライブラリを用います。pipなど用いてインストールしておきます。詳細は下記参照ください。
はじめに資産配分比率の下限・上限制約なしで解いてみます。
下限・上限制約なし
ポートフォリオの分散最小化として、要求リターン3.0%から8.5%までのポートフォリオのリスク(標準偏差)と資産配分比率を計算します。
# 分散最小化
ef = EfficientFrontier(r, df)
trets = np.arange(0.030, 0.085, 0.001)
tvols = []
tweights = []
for tr in trets:
w = ef.efficient_return(target_return=tr)
w = pd.Series(w).values
v = np.sqrt(np.dot(w.T, np.dot(cov, w)))
tvols += [v]
tweights += [w]
ポートフォリオのリスク・リターン及び各資産のリスク・リターンを描きます。
# 各資産のリスク・リターン
for (i,j,k) in zip(risk,r,assetName):
plt.scatter(i,j)
plt.annotate(k, xy=(i, j))
# ポートフォリオのリスク・リターン(効率的フロンティア)
plt.scatter(tvols, trets, marker='x')
plt.xlabel('risk')
plt.ylabel('expected return')
plt.show()

資産を組み合わせことによって、リスクが低減されている(同じ期待リターンのとき、1資産のときよりポートフォリオのリスクは小さい)ことを確認できます。
各資産配分比率をみてみます。
dfWeights = pd.DataFrame(tweights,index = trets, columns = assetName)
dfWeights.plot.area()
plt.xlabel("expected return")
plt.ylabel("weight")
plt.ylim([0,1])

不動産・金がほぼ投資されていなかったり、1資産への配分比率が大きくなっています。そこで、各資産への配分比率に下限・上限制約をつけて最適化します。
下限・上限制約あり
WealthNaviでは、資産クラスごとに下限・上限制約を設けていますが、ここでは、資産一律下限を5%、上限を35%として最適化します。
制約条件が増えると、最適化の実現可能領域が小さくなるので、要求リターンを4%から7%までに変更して実行します。
# 分散最小化
ef = EfficientFrontier(r, df, weight_bounds=(0.05, 0.35))
trets = np.arange(0.04, 0.07, 0.001)
tvols = []
tweights = []
for tr in trets:
w = ef.efficient_return(target_return=tr)
w = pd.Series(w).values
v = np.sqrt(np.dot(w.T, np.dot(cov, w)))
tvols += [v]
tweights += [w]
# 各資産のリスク・リターン
for (i,j,k) in zip(risk,r,assetName):
plt.scatter(i,j)
plt.annotate(k, xy=(i, j))
# ポートフォリオのリスク・リターン(効率的フロンティア)
plt.scatter(tvols2, trets2, marker='x')
plt.xlabel('risk')
plt.ylabel('expected return')
plt.show()

dfWeights2 = pd.DataFrame(tweights2, index= trets2,columns = assetName)
dfWeights2.plot.area()
plt.xlabel("expected return")
plt.ylabel("weight")
plt.ylim([0,1])

どの要求リターンのポートフォリオでも、不動産・金が下限制約の5%で配分されるようになりました。
さいごに
PyPortfolioOptというライブラリを使って、簡単にポートフォリオの最適化問題を実行することができました。公式サイトをみると、リスク最小化だけでなく、シャープレシオやWealth Naviと同じリスク許容度を考慮したポートフォリオリターンの最大化問題として、解くこともできます。
ポートフォリオの最適化問題は、インプットの期待リターンによって資産配分が大きく変わるので、各資産の期待リターンを推定するのが課題です。Wealth Naviで用いられているブラック・リッターマンモデルや時系列分析などで期待リターンを推定してみたいと思います。