理科系の勉強日記

Linux/Ubuntu/Mac/Emacs/Computer vision/Robotics

ポートフォリオ最適化で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$以上を求めるには、以下の条件で最適化を解くことになります。この最適化は非線形最適化のうち二次計画法で解ける形です。



\begin{aligned}
& \text{minimize } && \boldsymbol{x}^{T}C\boldsymbol{x} \\
& \text{subject to} && \sum_{i=1}^{N}{x_i} = 1 \\
& && \sum_{i=1}^{N}{m_i x_i} \geq r \\
& && 0 \leq x_i \leq 1 &&& (i = 1, \dots, N)
\end{aligned}


まずは米株のデータを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) # 銘柄数

f:id:kenbell1988:20200209172954p:plainf:id:kenbell1988:20200209172951p:plainf:id:kenbell1988:20200209172948p:plain
APPLの株価と日々の収益率


ある銘柄の期待収益率は、ある期間での日々の収益率の平均値に日数を掛けたものとしました。各銘柄の収益率の共分散行列も求めます。こちらは日々の収益率の分散から計算しています。分散を最小化するポートフォリオには少なくとも15%程度の収益率を持たせることにしました。

# 期待(?)収益率 = 全区間の収益率の平均値に任意の期間を掛けたもの
duration = 252 # [days]
m = df_return.mean().values * duration

# 日々の収益率の共分散行列
cov = df_return.cov().values 

# 最適化するPFの収益率の最小値
r = 0.15 

f:id:kenbell1988:20200209173827p:plain
各銘柄の収益率の共分散行列

以上でポートフォリオの分散を最小にする銘柄選択のための数字が準備できました。これをCVXOPTで計算し、どの銘柄をどれだけの割合で持てばいいかを計算してみます。目的関数と制約条件をCVXOPTに読み込ませるため、多少の式変形を行います。CVXOPTが想定する2次計画問題は以下の形となっています。



\begin{aligned}
& \text{minimize } && \frac{1}{2}\boldsymbol{x}^{T}P\boldsymbol{x} + \boldsymbol{q} \boldsymbol{x}\\
& \text{subject to} && G\boldsymbol{x} \leq \boldsymbol{h} \\
& && A\boldsymbol{x} = \boldsymbol{b}
\end{aligned}

# 最適化ソルバへの入力を準備
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でよく似たことをやっている記事がありました。先に見なくてよかった。。。