ポートフォリオ最適化でCVXOPTを学ぶ
リターンを確保しつつリスクを最小にするポートフォリオを構成するにはどの銘柄をどれだけ持てばいいのでしょう?
ポートフォリオに銘柄$i, (i = 1, \dots, N)$をどれだけの割合もつかということを$\boldsymbol{x} = [ x_1, \dots, x_N ]^{T}$、各銘柄のリターンの共分散行列を$C$とすると、ポートフォリオ全体の分散は$\boldsymbol{x}^{T}C\boldsymbol{x}$となります。各銘柄の期待リターンを$\boldsymbol{m} = [ m_1, \dots, m_N ]^{T}$とすると、ポートフォリオ全体のリターンは$\sum_{i=1}^{N}{m_i x_i}$ですね。ポートフォリオの分散を最小化しつつリターン$r$以上を求めるには、以下の条件で最適化を解くことになります。この最適化は非線形最適化のうち二次計画法で解ける形です。
まずは米株のデータをQuandlで取得します。収益率は前日との変化率としました。
from datetime import datetime import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import quandl start = datetime(2010, 1, 1) end = datetime(2018, 12, 31) symbls = [ # DOW30種 "AAPL", "AXP", "BA", "CAT", "CSCO", "CVX", # 中略 "WMT", "XOM" ] # Quadlで株価取得 codes = ['WIKI/' + symbl for symbl in symbls] data = quandl.get(codes, start_date=start, end_date=end) types = [code + " - Adj. Close" for code in codes] df = data[types] df.columns = symbls df_return = df.pct_change().dropna() # 収益率 N = len(df.columns) # 銘柄数
ある銘柄の期待収益率は、ある期間での日々の収益率の平均値に日数を掛けたものとしました。各銘柄の収益率の共分散行列も求めます。こちらは日々の収益率の分散から計算しています。分散を最小化するポートフォリオには少なくとも15%程度の収益率を持たせることにしました。
# 期待(?)収益率 = 全区間の収益率の平均値に任意の期間を掛けたもの duration = 252 # [days] m = df_return.mean().values * duration # 日々の収益率の共分散行列 cov = df_return.cov().values # 最適化するPFの収益率の最小値 r = 0.15
以上でポートフォリオの分散を最小にする銘柄選択のための数字が準備できました。これをCVXOPTで計算し、どの銘柄をどれだけの割合で持てばいいかを計算してみます。目的関数と制約条件をCVXOPTに読み込ませるため、多少の式変形を行います。CVXOPTが想定する2次計画問題は以下の形となっています。
# 最適化ソルバへの入力を準備 g1 = np.eye(N) g2 = -np.eye(N) G = np.vstack((g1, g2)) G = np.vstack((G, -m)) # h = [1,1,...1, 0,0,...,0,-r] h1 = np.ones(N) h2 = np.zeros(N) h = np.hstack((h1, h2)) h = np.hstack((h, -r))
最後にCVXOPTの最適化計算を行います。
import cvxopt from cvxopt import matrix cvxopt.solvers.options['show_progress'] = False P = matrix(cov) q = matrix(np.zeros(N)) A = matrix(np.ones(N).reshape(1,N)) b = matrix(1.0) G = matrix(G) h = matrix(h) sol = cvxopt.solvers.qp(P,q, A=A, b=b,G=G, h=h) print("Optimized Portfolio") for i, e in enumerate(sol["x"]): if e > 10e-3: print(f"{df_return.columns[i]}\t: {e:.4f}[%]") risk_e = 2 * sol["primal objective"] x_e = np.array(list(sol["x"])) r_e = m.dot(x_e) print("-------------------") print(f"PF variance \t: {risk_e:.8f}") print(f"PF std \t\t: {np.sqrt(risk_e):.8f}") print(f"PF return \t: {r_e:.8f}") # ===> Result # Optimized Portfolio # BA : 4.46[%] # CVX : 2.35[%] # DOW : 32.37[%] # KO : 11.50[%] # MCD : 4.10[%] # MMM : 30.72[%] # NKE : 2.94[%] # PG : 2.70[%] # TRV : 3.79[%] # WMT : 3.69[%] # ------------------- # PF variance : 0.00001859 # PF std : 0.00431213 # PF return : 0.15000158
収益率を15%以上に保ちながら分散を最小にするには、以上のように銘柄を選択すればいいようです。この計算を信じる限りは。
。。。
問題は各銘柄の期待収益率、収益率の分散や共分散行列がどう求めるかでしょうね。
やってから検索してQiitaでよく似たことをやっている記事がありました。先に見なくてよかった。。。