Loading [MathJax]/jax/output/CommonHTML/jax.js

はじめに

nAG Library for Python Example集

このページは、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問題は以下の形式を取ります minimizexn12xTP0x+qT0x+r0subject to12xTPix+qTix+ri0,i=1,,p. これは現代ポートフォリオ理論、機械学習、工学、制御などの応用分野で現れます。凸QCQPは通常、計算効率と不可能性の検出能力により、円錐最適化、より正確には二次錐計画法(SOCP)を通じて扱われます。しかし、SOCPを使用して凸QCQPを解くのは簡単ではなく、問題データの変換と補助変数の追加に余分な労力が必要です。このノートブックでは、nAGライブラリのnAG最適化モデリングスイートを使用して、ポートフォリオ最適化におけるQCQPを定義し解く方法を示します。

データ準備

2018年3月から2019年3月までのDJIAの30銘柄の日次価格を考えます。実際には、平均リターンrと共分散Vの推定は多くの場合簡単ではありません。このノートブックでは、これらの要素を単純なサンプル推定を用いて推定します。

# 必要なライブラリをインポート
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 = stock_price = pkl.load(open('./data/stock_price.pkl', 'rb'))
close_price = stock_price['close_price']
date_index = stock_price['date_index']
# データのサイズ、m: 観測数、n: 株式数
m = len(date_index)
n = len(close_price)
# 株価の終値をnumpy配列に抽出する
data = np.zeros(shape=(m, n))
i = 0
for stock in close_price:
    data[:,i] = close_price[stock]
    plt.plot(np.arange(m), data[:,i])
    i += 1
# 終値をプロットする
plt.xlabel('Time (days)')
plt.ylabel('Closing price ($)')
plt.show()
png

各銘柄 i について、まず j 日目の日次相対リターンを以下のように推定します: relative returni,j=closing pricei,j+1closing pricei,jclosing pricei,j.

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

各株式の平均リターン r を得るために、相対リターンの各列の算術平均を単純に取り、その後、numpyを使用して共分散 V を推定します。

# 平均リターン
r = np.zeros(n)
r = rel_rtn.sum(axis=0)
r = r / (m-1)
# 共分散行列
V = np.cov(rel_rtn.T)

古典的な平均分散モデル

効率的フロンティア

ポートフォリオ管理の主要な目標の1つは、特定のリスク測定の下で一定水準のリターンを達成することです。ここでは、nAGライブラリを使用して、ロングオンリー制約(つまり、買い持ちのみで空売りは許可されない)を持つ古典的なマーコビッツモデルを解くことで、効率的フロンティアを構築する方法を示します: minimizexnrTx+μxTVxsubject toeTx=1,x0, ここで、enはすべて1のベクトルで、μはリターンとリスクのトレードオフを制御するスカラーです。μを0から特定の値まで変化させることで効率的フロンティアを構築できることに注意してください。

# nAGライブラリをインポートする
from naginterfaces.base import utils
from naginterfaces.library import opt
# 必要な数学ライブラリをインポートする
import math as mt
import warnings as wn
# 2次目的関数の入力
# 上三角行列Vの疎行列パターン
irowq, icolq = np.nonzero(np.triu(V))
v_val = V[irowq, icolq]
# 1ベースに変換
irowq = irowq + 1
icolq = icolq + 1
# この応用では実際に密である r のスパース性パターン
idxr = np.arange(1, n+1)

# 線形制約条件の入力: e'x = 1
irowa = np.full(n, 1, dtype=int)
icola = np.arange(1, n+1)
a = np.full(n, 1.0, dtype=float)
bl = np.full(1, 1.0, dtype=float)
bu = np.full(1, 1.0, dtype=float)

# 境界制約の入力: x >= 0
blx = np.zeros(n)
bux = np.full(n, 1.e20, float)

入力データの準備ができました。以下のように効率的フロンティアを簡単に構築できます。

# muのステップを設定
step = 2001

# 出力データを初期化: 絶対リスクとリターン
ab_risk = np.empty(0, float)
ab_rtn = np.empty(0, float)

for mu in np.linspace(0.0, 2000.0, step):
    # 問題ハンドルを作成する
    handle = opt.handle_init(n)
    
    # 2次目的関数を設定
    # QCQPの標準形式では、qは2*mu*Vであるべきです
    q = 2.0 * mu * v_val
    idqc = -1
    opt.handle_set_qconstr(handle, 0.0, idqc, idxr, -r, irowq, icolq, q)
    
    # 線形制約 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ソルバーを呼び出す
    # 警告をミュートし、警告からの結果をカウントしない
    wn.simplefilter('error', utils.NagAlgorithmicWarning)
    try:
        slt = opt.handle_solve_socp_ipm(handle)

        # ポートフォリオのリスクとリターンを計算する
        ab_risk = np.append(ab_risk, mt.sqrt(slt.x[0:n].dot(V.dot(slt.x[0:n]))))
        ab_rtn = np.append(ab_rtn, r.dot(slt.x[0:n]))
    except utils.NagAlgorithmicWarning:
        pass
    
    # ハンドルを破棄する:
    opt.handle_free(handle)
# 結果をプロットする
plt.plot(ab_risk*100.0, ab_rtn*100.0)
plt.ylabel('Total Expected Return (%)')
plt.xlabel('Absolute Risk (%)')
plt.show()
png

シャープレシオの最大化

シャープレシオは、ポートフォリオのリターンとポートフォリオの超過リターンの標準偏差の比として定義されます。通常、ポートフォリオの効率性を測定するために使用されます。最も効率的なポートフォリオを見つけることは、以下の最適化問題を解くことと同等です。 minimizexnxTVxrTxsubject toeTx=1,x0. xyλ,λ>0で置き換えることで、モデル(sr_model)は以下と同等になります: minimizeyn,λyTVysubject toeTy=λ,rTy=1,y0,λ0. 問題(sr_model_eq)は、二次の目的関数と線形制約を持つという点で問題(MV_model)と類似しています。

# 線形制約の入力: e'y = lambda
irowa = np.full(n+1, 1, dtype=int)
icola = np.arange(1, n+2)
a = np.append(np.full(n, 1.0, dtype=float), -1.0)
bl = np.zeros(1)
bu = np.zeros(1)

# 線形制約の入力: r'y = 1
irowa = np.append(irowa, np.full(n, 2, dtype=int))
icola = np.append(icola, np.arange(1, n+1))
a = np.append(a, r)
bl = np.append(bl, 1.0)
bu = np.append(bu, 1.0)

# 境界制約の入力: x >= 0
blx = np.zeros(n+1)
bux = np.full(n+1, 1.e20, float)

これで以下のようにnAG SOCPソルバーを呼び出すことができます。

# 問題ハンドルを作成する
handle = opt.handle_init(n+1)

# 2次目的関数を設定する
# QCQPの標準形式では、qは2*Vでなければなりません
q = 2.0 * v_val
idqc = -1
opt.handle_set_qconstr(handle, 0.0, idqc, irowq=irowq, icolq=icolq, q=q)

# 線形制約を設定する
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ソルバーを呼び出す
slt = opt.handle_solve_socp_ipm(handle)

sr_risk = mt.sqrt(slt.x[0:n].dot(V.dot(slt.x[0:n])))/slt.x[n]
sr_rtn = r.dot(slt.x[0:n])/slt.x[n]
sr_x = slt.x[0:n]/slt.x[n]

# ハンドルを破棄する:
opt.handle_free(handle)
# 結果をプロットする。
plt.plot(ab_risk*100.0, ab_rtn*100.0, label='Efficient frontier')
plt.plot([sr_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.axis([min(ab_risk*100), max(ab_risk*100), min(ab_rtn*100), max(ab_rtn*100)])
plt.ylabel('Total Expected Return (%)')
plt.xlabel('Absolute Risk (%)')
plt.legend()
plt.show()
png

トラッキングエラー制約付きポートフォリオ最適化

ベンチマークを上回る際に不必要なリスクを取ることを避けるため、投資家は一般的にアクティブポートフォリオのベンチマークからの乖離の変動性に制限を設けます。これはトラッキングエラー変動性(TEV)としても知られています。超過リターン空間で効率的フロンティアを構築するモデルは以下の通りです: maximizexnrTxsubject toeTx=0,xTVxtev, ここでtevはトラッキングエラーの制限です。Roll は、問題(er_tev)がベンチマークと完全に独立しており、アクティブポートフォリオが体系的にベンチマークよりも高いリスクを持ち、最適ではないという好ましくない結果につながると指摘しました。そのため、このセクションでは絶対リスクを考慮したより高度なモデルを以下のように解きます。 minimizexnrTx+μ(x+b)TV(x+b)subject toeTx=0,xTVxtev,x+b0, ここでbはベンチマークポートフォリオです。このデモンストレーションでは、これは人工的に生成されています。ここでは、デモンストレーションの目的で、tevと絶対リスク測定に同じ共分散行列Vを使用していることに注意してください。実際には、異なる市場から異なる共分散行列を使用することができます。

# シャープレシオを最大化する効率的ポートフォリオからベンチマークポートフォリオを生成する
# xを摂動させる
b = sr_x + 1.e-1
# bを正規化する
b = b/sum(b)

# トラッキングエラーの制限を設定する
tev = 0.000002

# ベンチマークにおけるリスクとリターンを計算する
b_risk = mt.sqrt(b.dot(V.dot(b)))
b_rtn = r.dot(b)
# 線形制約条件の入力: e'x = 0
irowa = np.full(n, 1, dtype=int)
icola = np.arange(1, n+1)
a = np.full(n, 1.0, dtype=float)
bl = np.zeros(1)
bu = np.zeros(1)

# 境界制約の入力: x >= -b
blx = -b
bux = np.full(n, 1.e20, float)
# 出力データの初期化: TEVリスクとリターン
tev_risk = np.empty(0, float)
tev_rtn = np.empty(0, float)

for mu in np.linspace(0.0, 2000.0, step):
    # 問題ハンドルを作成する
    handle = opt.handle_init(n)
    
    # 2次目的関数を設定
    # QCQPの標準形式では、qは2*mu*Vであるべきです
    q = 2.0 * mu * v_val
    r_mu = 2.0*mu*V.dot(b)-r
    idqc = -1
    opt.handle_set_qconstr(handle, 0.0, idqc, idxr, r_mu, irowq, icolq, q)
    
    # 二次制約を設定
    # QCQPの標準形式では、qは2*Vでなければなりません
    q = 2.0 * v_val
    idqc = 0
    opt.handle_set_qconstr(handle, -tev, idqc, irowq=irowq, icolq=icolq, q=q)
    
    # 線形制約 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ソルバーを呼び出す
    # 警告をミュートし、警告からの結果をカウントしない
    wn.simplefilter('error', utils.NagAlgorithmicWarning)
    try:
        slt = opt.handle_solve_socp_ipm(handle)

# ポートフォリオのリスクとリターンを計算する
        tev_risk = np.append(tev_risk, mt.sqrt((slt.x[0:n]+b).dot(V.dot(slt.x[0:n]+b))))
        tev_rtn = np.append(tev_rtn, r.dot(slt.x[0:n]+b))
    except utils.NagAlgorithmicWarning:
        pass
    
    # ハンドルを破棄する:
    opt.handle_free(handle)
# 結果をプロットする
plt.figure(figsize=(7.5, 5.5))
plt.plot(ab_risk*100.0, ab_rtn*100.0, label='Classic efficient frontier')
plt.plot([sr_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(b_risk*100, b_rtn*100, 'r*', label='Benchmark portfolio')
plt.plot(tev_risk*100.0, tev_rtn*100.0, 'seagreen', label='Efficient frontier with tev constraint')

plt.axis([min(ab_risk*100), max(ab_risk*100), min(tev_rtn*100), max(ab_rtn*100)])
plt.ylabel('Total Expected Return (%)')
plt.xlabel('Absolute Risk (%)')
plt.legend()
plt.show()
png

結論

このノートブックでは、ポートフォリオ最適化におけるさまざまな二次モデルを解くために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.

関連情報
MENU
© 日本ニューメリカルアルゴリズムズグループ株式会社 2025
Privacy Policy  /  Trademarks