このページは、nAGライブラリのJupyterノートブックExampleの日本語翻訳版です。オリジナルのノートブックはインタラクティブに操作することができます。
二次制約付き二次計画法とそのポートフォリオ最適化への応用
このノートブックの正しいレンダリング
このノートブックでは、方程式と参照のためにlatex_envs
Jupyter拡張機能を使用しています。LaTeXがローカルのJupyterインストールで正しくレンダリングされない場合は、この拡張機能をインストールしていない可能性があります。詳細は
https://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/latex_envs/README.html
を参照してください。
また、このノートブックはGitHubでは適切にレンダリングされないため、GitHubで閲覧している場合は、PDF版を参照することをお勧めします。
はじめに
二次制約付き二次計画法(QCQP)は、目的関数と制約条件の両方に二次関数が含まれる最適化問題の一種です。一般的なQCQP問題は以下の形式を取ります
データ準備
2018年3月から2019年3月までのDJIAの30銘柄の日次価格を考えます。実際には、平均リターン
# 必要なライブラリをインポート
import pickle as pkl
import numpy as np
import matplotlib.pyplot as plt
# stock_price.plkから株価データを読み込む
# Stock_price: dict = ['close_price': [data], 'date_index': [data]]
= stock_price = pkl.load(open('./data/stock_price.pkl', 'rb'))
stock_price = stock_price['close_price']
close_price = stock_price['date_index'] date_index
# データのサイズ、m: 観測数、n: 株式数
= len(date_index)
m = len(close_price) n
# 株価の終値をnumpy配列に抽出する
= np.zeros(shape=(m, n))
data = 0
i for stock in close_price:
= close_price[stock]
data[:,i]
plt.plot(np.arange(m), data[:,i])+= 1
i # 終値をプロットする
'Time (days)')
plt.xlabel('Closing price ($)')
plt.ylabel( plt.show()

各銘柄
# 相対リターン
= np.zeros(shape=(m-1, n))
rel_rtn for j in range(m-1):
= np.divide(data[j+1,:] - data[j,:], data[j,:])
rel_rtn[j,:] # 相対リターンをプロット
for i in range(n):
-1),rel_rtn[:,i])
plt.plot(np.arange(m'Time (days)')
plt.xlabel('Relative return')
plt.ylabel( plt.show()

各株式の平均リターン
# 平均リターン
= np.zeros(n)
r = rel_rtn.sum(axis=0)
r = r / (m-1)
r # 共分散行列
= np.cov(rel_rtn.T) V
古典的な平均分散モデル
効率的フロンティア
ポートフォリオ管理の主要な目標の1つは、特定のリスク測定の下で一定水準のリターンを達成することです。ここでは、nAGライブラリを使用して、ロングオンリー制約(つまり、買い持ちのみで空売りは許可されない)を持つ古典的なマーコビッツモデルを解くことで、効率的フロンティアを構築する方法を示します:
# nAGライブラリをインポートする
from naginterfaces.base import utils
from naginterfaces.library import opt
# 必要な数学ライブラリをインポートする
import math as mt
import warnings as wn
# 2次目的関数の入力
# 上三角行列Vの疎行列パターン
= np.nonzero(np.triu(V))
irowq, icolq = V[irowq, icolq]
v_val # 1ベースに変換
= irowq + 1
irowq = icolq + 1
icolq # この応用では実際に密である r のスパース性パターン
= np.arange(1, n+1)
idxr
# 線形制約条件の入力: e'x = 1
= np.full(n, 1, dtype=int)
irowa = np.arange(1, n+1)
icola = np.full(n, 1.0, dtype=float)
a = np.full(1, 1.0, dtype=float)
bl = np.full(1, 1.0, dtype=float)
bu
# 境界制約の入力: x >= 0
= np.zeros(n)
blx = np.full(n, 1.e20, float) bux
入力データの準備ができました。以下のように効率的フロンティアを簡単に構築できます。
# muのステップを設定
= 2001
step
# 出力データを初期化: 絶対リスクとリターン
= np.empty(0, float)
ab_risk = np.empty(0, float)
ab_rtn
for mu in np.linspace(0.0, 2000.0, step):
# 問題ハンドルを作成する
= opt.handle_init(n)
handle
# 2次目的関数を設定
# QCQPの標準形式では、qは2*mu*Vであるべきです
= 2.0 * mu * v_val
q = -1
idqc 0.0, idqc, idxr, -r, irowq, icolq, q)
opt.handle_set_qconstr(handle,
# 線形制約 e'x = 1 を設定する
opt.handle_set_linconstr(handle, bl, bu, irowa, icola, a)
# 境界制約を設定
opt.handle_set_simplebounds(handle, blx, bux)
# オプションを設定
for option in [
'Print Options = NO',
'Print Level = 1',
'Print File = -1',
'SOCP Scaling = A'
]:
opt.handle_opt_set(handle, option)
# 内点法SOCPソルバーを呼び出す
# 警告をミュートし、警告からの結果をカウントしない
'error', utils.NagAlgorithmicWarning)
wn.simplefilter(try:
= opt.handle_solve_socp_ipm(handle)
slt
# ポートフォリオのリスクとリターンを計算する
= np.append(ab_risk, mt.sqrt(slt.x[0:n].dot(V.dot(slt.x[0:n]))))
ab_risk = np.append(ab_rtn, r.dot(slt.x[0:n]))
ab_rtn except utils.NagAlgorithmicWarning:
pass
# ハンドルを破棄する:
opt.handle_free(handle)
# 結果をプロットする
*100.0, ab_rtn*100.0)
plt.plot(ab_risk'Total Expected Return (%)')
plt.ylabel('Absolute Risk (%)')
plt.xlabel( plt.show()

シャープレシオの最大化
シャープレシオは、ポートフォリオのリターンとポートフォリオの超過リターンの標準偏差の比として定義されます。通常、ポートフォリオの効率性を測定するために使用されます。最も効率的なポートフォリオを見つけることは、以下の最適化問題を解くことと同等です。
# 線形制約の入力: e'y = lambda
= np.full(n+1, 1, dtype=int)
irowa = np.arange(1, n+2)
icola = np.append(np.full(n, 1.0, dtype=float), -1.0)
a = np.zeros(1)
bl = np.zeros(1)
bu
# 線形制約の入力: r'y = 1
= np.append(irowa, np.full(n, 2, dtype=int))
irowa = np.append(icola, np.arange(1, n+1))
icola = np.append(a, r)
a = np.append(bl, 1.0)
bl = np.append(bu, 1.0)
bu
# 境界制約の入力: x >= 0
= np.zeros(n+1)
blx = np.full(n+1, 1.e20, float) bux
これで以下のようにnAG SOCPソルバーを呼び出すことができます。
# 問題ハンドルを作成する
= opt.handle_init(n+1)
handle
# 2次目的関数を設定する
# QCQPの標準形式では、qは2*Vでなければなりません
= 2.0 * v_val
q = -1
idqc 0.0, idqc, irowq=irowq, icolq=icolq, q=q)
opt.handle_set_qconstr(handle,
# 線形制約を設定する
opt.handle_set_linconstr(handle, bl, bu, irowa, icola, a)
# 境界制約を設定
opt.handle_set_simplebounds(handle, blx, bux)
# オプションを設定
for option in [
'Print Options = NO',
'Print Level = 1',
'Print File = -1',
'SOCP Scaling = A'
]:
opt.handle_opt_set(handle, option)
# 内点法SOCPソルバーを呼び出す
= opt.handle_solve_socp_ipm(handle)
slt
= mt.sqrt(slt.x[0:n].dot(V.dot(slt.x[0:n])))/slt.x[n]
sr_risk = r.dot(slt.x[0:n])/slt.x[n]
sr_rtn = slt.x[0:n]/slt.x[n]
sr_x
# ハンドルを破棄する:
opt.handle_free(handle)
# 結果をプロットする。
*100.0, ab_rtn*100.0, label='Efficient frontier')
plt.plot(ab_risk*100], [sr_rtn*100], 'rs', label='Portfolio with maximum Sharpe ratio')
plt.plot([sr_risk*100, 0.0], [sr_rtn*100, 0.0], 'r-', label='Capital market line')
plt.plot([sr_riskmin(ab_risk*100), max(ab_risk*100), min(ab_rtn*100), max(ab_rtn*100)])
plt.axis(['Total Expected Return (%)')
plt.ylabel('Absolute Risk (%)')
plt.xlabel(
plt.legend() plt.show()

トラッキングエラー制約付きポートフォリオ最適化
ベンチマークを上回る際に不必要なリスクを取ることを避けるため、投資家は一般的にアクティブポートフォリオのベンチマークからの乖離の変動性に制限を設けます。これはトラッキングエラー変動性(TEV)としても知られています。超過リターン空間で効率的フロンティアを構築するモデルは以下の通りです:
# シャープレシオを最大化する効率的ポートフォリオからベンチマークポートフォリオを生成する
# xを摂動させる
= sr_x + 1.e-1
b # bを正規化する
= b/sum(b)
b
# トラッキングエラーの制限を設定する
= 0.000002
tev
# ベンチマークにおけるリスクとリターンを計算する
= mt.sqrt(b.dot(V.dot(b)))
b_risk = r.dot(b) b_rtn
# 線形制約条件の入力: e'x = 0
= np.full(n, 1, dtype=int)
irowa = np.arange(1, n+1)
icola = np.full(n, 1.0, dtype=float)
a = np.zeros(1)
bl = np.zeros(1)
bu
# 境界制約の入力: x >= -b
= -b
blx = np.full(n, 1.e20, float) bux
# 出力データの初期化: TEVリスクとリターン
= np.empty(0, float)
tev_risk = np.empty(0, float)
tev_rtn
for mu in np.linspace(0.0, 2000.0, step):
# 問題ハンドルを作成する
= opt.handle_init(n)
handle
# 2次目的関数を設定
# QCQPの標準形式では、qは2*mu*Vであるべきです
= 2.0 * mu * v_val
q = 2.0*mu*V.dot(b)-r
r_mu = -1
idqc 0.0, idqc, idxr, r_mu, irowq, icolq, q)
opt.handle_set_qconstr(handle,
# 二次制約を設定
# QCQPの標準形式では、qは2*Vでなければなりません
= 2.0 * v_val
q = 0
idqc -tev, idqc, irowq=irowq, icolq=icolq, q=q)
opt.handle_set_qconstr(handle,
# 線形制約 e'x = 1 を設定する
opt.handle_set_linconstr(handle, bl, bu, irowa, icola, a)
# 境界制約を設定
opt.handle_set_simplebounds(handle, blx, bux)
# オプションを設定
for option in [
'Print Options = NO',
'Print Level = 1',
'Print File = -1',
'SOCP Scaling = A'
]:
opt.handle_opt_set(handle, option)
# 内点法SOCPソルバーを呼び出す
# 警告をミュートし、警告からの結果をカウントしない
'error', utils.NagAlgorithmicWarning)
wn.simplefilter(try:
= opt.handle_solve_socp_ipm(handle)
slt
# ポートフォリオのリスクとリターンを計算する
= np.append(tev_risk, mt.sqrt((slt.x[0:n]+b).dot(V.dot(slt.x[0:n]+b))))
tev_risk = np.append(tev_rtn, r.dot(slt.x[0:n]+b))
tev_rtn except utils.NagAlgorithmicWarning:
pass
# ハンドルを破棄する:
opt.handle_free(handle)
# 結果をプロットする
=(7.5, 5.5))
plt.figure(figsize*100.0, ab_rtn*100.0, label='Classic efficient frontier')
plt.plot(ab_risk*100], [sr_rtn*100], 'rs', label='Portfolio with maximum Sharpe ratio')
plt.plot([sr_risk*100, 0.0], [sr_rtn*100, 0.0], 'r-', label='Capital market line')
plt.plot([sr_risk*100, b_rtn*100, 'r*', label='Benchmark portfolio')
plt.plot(b_risk*100.0, tev_rtn*100.0, 'seagreen', label='Efficient frontier with tev constraint')
plt.plot(tev_risk
min(ab_risk*100), max(ab_risk*100), min(tev_rtn*100), max(ab_rtn*100)])
plt.axis(['Total Expected Return (%)')
plt.ylabel('Absolute Risk (%)')
plt.xlabel(
plt.legend() plt.show()

結論
このノートブックでは、ポートフォリオ最適化におけるさまざまな二次モデルを解くためにnAGライブラリを使用する方法を示しました。凸QCQPを解くには、通常、錐最適化が良い選択肢です。ここで言及したQCQPモデルに限らず、SOCPの汎用性は非常に広範囲に及ぶことを指摘しておく価値があります。より多くの問題や制約をカバーしています。例えば、DeMiguelらはノルム制約付きのポートフォリオ最適化について議論しており、これは容易にSOCP問題に変換できます。詳細については、SOCPソルバーに関するnAGライブラリのドキュメントおよびを参照してください。
参考文献
[1] Jorion Philippe, トラッキングエラー制約付きポートフォリオ最適化’’, Financial Analysts Journal, vol. 59, number 5, pp. 70–82, 2003.
[2] Roll Richard, トラッキングエラーの平均/分散分析’’, The Journal of Portfolio Management, vol. 18, number 4, pp. 13–22, 1992.
[3] DeMiguel Victor, Garlappi Lorenzo, Nogales Francisco J 他, ポートフォリオ最適化への一般化アプローチ:ポートフォリオノルムを制約することによるパフォーマンス向上’’, Management science, vol. 55, number 5, pp. 798–812, 2009.
[4] Numerical Algorithms Group, nAGドキュメント’’, 2019. オンライン
[5] Alizadeh Farid and Goldfarb Donald, 二次錐計画法’’, Mathematical programming, vol. 95, number 1, pp. 3–51, 2003.
[6] Lobo Miguel Sousa, Vandenberghe Lieven, Boyd Stephen 他, 二次錐計画法の応用’’, Linear algebra and its applications, vol. 284, number 1-3, pp. 193–228, 1998.