ショートフォールを考慮したポートフォリオ最適化#

導入#

投資において目標とする資産額あるいは投資リターンに到達しない確率を、ショートフォールリスクと呼びます。ポートフォリオ最適化に期待ショートフォールのリスク指標を導入すると、ダイナミックに市場が動くような極端なモデルでもリターンを考えることが可能になります。目標リターンはMarkowitzのポートフォリオ最適化モデルの制約条件として扱い、目標とするショートフォールを満たすように目標リターンを動的に調整する反復アルゴリズムが、Xu et al., 2023により提案されました。今回はこれを題材に、JijModelingとJijSolverの使い方を学びましょう。

動的資産配分 (Dynamic Asset Allocation)#

動的資産配分問題とは、リスクを所定の閾値以下に抑えながら期待リターンを満たすように、\(N\)個の資産に資金を配分/投資する問題です。この問題の入力には次のようなものが用いられます。これまでのリターンを表した行列\(R\)をファイナンスデータから取得します。この行列は\(N\)\(D_\mathrm{total}\)列で表されます。ここで\(N\)は対象とする銘柄数、\(D_\mathrm{total}\)は対象とする日数です。これを\(T\)の期間に分け、各期間のデータにインデックスを振り分けます。例えば\(R_t\)は、\(t\)番目の期間のリターンを表します。そしてその\(R_t\)から計算される平均を\(\mu_t\)とすると、\(t\)番目の共分散行列は

\[ C_{t, i, j} = \frac{(\mathbf{e}_i^\top R_t - \mu^\top_{t, i} \mathbf{1}) \cdot (\mathbf{e}_j^\top R_t - \mu^\top_{t, j} \mathbf{1})}{T-1} \tag{1} \]

のように計算されます。ここで\(\mathbf{e}_i\)は、\(i\)番目の成分は1、それ以外の成分は0となるような列ベクトルです。
資産配分では、予測不可能な市場の乱高下がある時期には困難なものとなります。そのため、その時期のリスクを回避するための閾値を設けておかなければなりません。リスク閾値は、例えば2008年のリーマンショック時の市場暴落のような不安定な時期から求められた市場指標を用いて設定するなどの例があります。その閾値を上回っているかどうかのリスク測定手法の代表的なものには、以下のようなものがあります。

  • ボラティリティ: ポートフォリオリターンの標準偏差

  • \(\alpha\)レベルにおけるバリュー・アット・リスク: ポートフォリオが総予算の\(y\)%以上を失わない確率が、少なくとも\(1-\alpha\)となるような最小の\(y\)

  • \(\alpha\)レベルにおける期待ショートフォール: 最悪の\(\alpha\)%のケースから得られる期待収益

以降では、期待ショートフォールを用いた定式化を考えていきましょう。

期待ショートフォールを考慮したポートフォリオ最適化#

期待ショートフォール\(\mathrm{ES}_\alpha\)は以下のように計算されます。

\[ \mathrm{ES}_\alpha (w_t, R_t) = \mathrm{mean}(\mathrm{lowest} \ \alpha \% \ \mathrm{from} \ w_t^\top R_t) \tag{2} \]

ここで\(w_t\)は、\(t\)番目の期間において予算の何割を各資産に投資するかを表す重み付けベクトルです。最悪ケースの平均である期待ショートフォールは最小化したい対象となります。よって、これを考慮した場合のポートフォリオ最適化は、「全資産を投資するという制約下で\(\mathrm{ES}_\alpha\)を最小化しつつ、期待リターンを満足し、ポートフォリオ分散も小さくする」という問題となります。しかし、この期待ショートフォールは2次形式で表現することができないため、もしこれをQUBOにしようとすると補助変数などを新たに導入する必要が生まれます。 よってこの困難を回避するために、ここでは期待ショートフォールを収束基準として用いることにしましょう。 またその基準計算のために、これまでのリターンがガウス分布に従うと仮定し、与えられたポートフォリオ\(P\)の期待ショートフォールを以下のように近似して求めることにします。

\[ \mathrm{ES}_\alpha (P) = \mu + \sigma \frac{\phi (\Phi^{-1} (\alpha))}{1-\alpha} \tag{3} \]

\(\mu\)は期待されるリターン、\(\sigma\)はポートフォリオのボラティリティ、\(\phi(x)\)\(\Phi(x)\)はそれぞれガウス分布と累積分布関数を表します。
市場のリスクの大小を、この基準を用いて判定することにします。例えばリスクが小さい場合には、投資額を少し大きくしてその分大きなリターンを得たいと思うはずです。同様に、市場が不安定でリスクが大きい時期には、リターンを小さくする代わりに損をするリスクを下げる方法が取られるでしょう。このように、その時期の期待ショートフォールに合わせて動的にリターンを変動させるために、Xu et al., 2023では次のようなアルゴリズムを開発しました。

アルゴリズム: 期待ショートフォールをベースとした\(t\)番目の時期における動的資産配分
入力: \(\mu_t, R_t, \sigma_\mathrm{ref}, \sigma_{\mathrm{ref}, t}, \mathrm{ES}_\mathrm{ref}, \alpha\)
出力: \(w_t\)
1: \(p_t = \mathrm{mean} (\mu_t)\). // リターンを初期化
2: \(\mathrm{EST}_t = \frac{\sigma_\mathrm{ref}}{\sigma_{\mathrm{ref}, t}} \mathrm{ES}_\mathrm{ref}\). // 目標とする期待ショートフォールを初期化
3: \(C_t = \mathrm{cov}(R_t)\) // 共分散行列を計算
4: while True do:
5:   if \(p_t > \max (\mu_t)\):
6:     return 0. // 制約が満たされていない
7:   \(w_t\) = Markowitz(\(\mu_t, C_t, p_t\)). // Markowitzの定式化をアニーリングで解く
8:   \(\mathrm{ES}_t = \mathrm{ES}_\alpha (w_t, R_t)\). // 定義式(2)から期待ショートフォールを計算
9:   if \(\frac{\mathrm{ES}_t}{\mathrm{EST}_t} > 1 + \epsilon\):
10:    \(p_t = p_t (1-\delta)\). // もし期待ショートフォールが大きいなら、リターンを小さくして損するリスクを下げる
11:  else if: \(\frac{\mathrm{ES}_t}{\mathrm{EST}_t} < 1-\epsilon\)
12:    \(p_t = p_t (1+\delta)\). // もし期待ショートフォールが小さいなら、リターンを大きくする余裕がある
13:  else:
14:    return \(w_t\)

ここで\(\sigma_\mathrm{ref}\)は2008年の市場暴落時のボラティリティ、\(\sigma_{\mathrm{ref}, t}\)\(t\)番目の時期におけるボラティリティです。そして\(\mathrm{ES}_\mathrm{ref}\)は同じく2008年の市場暴落時の期待ショートフォールを表し、\(\mathrm{EST}_t\)\(t\)番目の時期における目標とする期待ショートフォールです。\(\alpha\)はリスクパラメータで、\(\mathrm{ES}_t\)\(t\)番目の時期においてMarkowitz定式化の最適化結果から計算される期待ショートフォール、\(\epsilon\)はエラー許容パラメータ、\(\delta\)は調整されるモメンタムパラメータを表します。途中で解いているMarkowitzの定式化とは、以下のようなものです。

\[\begin{split} \begin{align} \min_w & \quad w_t^\top C_t w_t \notag \\ \mathrm{s.t.} & \quad \mu_t^\top w_t \geq p_t \\ & \quad \sum_i w_{t, i} = 1, \quad w_{t, i} \geq 0 \quad (\forall i) \end{align} \tag{4} \end{split}\]

ここでは簡単のため、\(w_{t, i}\)\(\{0, 1\}\)のバイナリ変数であるとします。 制約条件\(\sum_i w_{t, i} = 1\)から、自分の持っている資産をある一つの銘柄に集中させ、リターンは常に\(p_t\)より大きくなるようにしています。

実装しましょう#

ここからは実際にJijModelingとJijSolverを用いて、この問題を解くスクリプトを実装しましょう。

変数の定義#

以下のようにして、(4)式の最適化問題を解くのに用いる変数を定義します。

import jijmodeling as jm

# define variables
N = jm.Placeholder('N')
p = jm.Placeholder('p')
mu = jm.Placeholder('mu', ndim=1)
C = jm.Placeholder('C', ndim=2)
w = jm.BinaryVar('w', shape=(N, ))
i = jm.Element('i', belong_to=N)
j = jm.Element('j', belong_to=N)

Nは購入を検討している銘柄数、pはリターンの下限値\(p_t\)muは各銘柄のリターン\(\mu_t\)Cは共分散行列\(C_t\)です。 そしてwは最適化に用いるバイナリ変数、i, jはそれぞれ添字に用いる変数です。

Markowitz定式化の実装#

(3)式で表現される、Markowitzのポートフォリオ最適化の数理モデルを実装しましょう。

# set a problem
problem = jm.Problem('shortfall')
# constraint 1: ensure the target return
problem += jm.Constraint('return', jm.sum(i, mu[i]*w[i])>=p)
# constraint 2: buy a stock
problem += jm.Constraint('onehot', jm.sum(i, w[i])==1)
# objective function: minimize variance
problem += jm.sum([i, j], C[i, j]*w[i]*w[j])

Problemで問題を作成し、その次にConstraintで2つの制約を実装します。 目的関数の\(\sum_i \sum_j C_{i, j} w_i w_j\)では、一つのsum([i, j], ...)のようにすることで2つの添字での総和を一度に計算することができます。
Jupyter Notebookであれば、実装された数理モデルを確認することができます。

problem
\[\begin{split}\begin{array}{cccc}\text{Problem:} & \text{shortfall} & & \\& & \min \quad \displaystyle \sum_{i = 0}^{N - 1} \sum_{j = 0}^{N - 1} C_{i, j} \cdot w_{i} \cdot w_{j} & \\\text{{s.t.}} & & & \\ & \text{onehot} & \displaystyle \sum_{i = 0}^{N - 1} w_{i} = 1 & \\ & \text{return} & \displaystyle \sum_{i = 0}^{N - 1} mu_{i} \cdot w_{i} \geq p & \\\text{{where}} & & & \\& w & 1\text{-dim binary variable}\\\end{array}\end{split}\]

JijSolverを用いて解く#

JijSolverを用いて、先ほど実装したMarkowitzのポートフォリオ最適化問題を解きましょう。 Xu et al, 2023で提案されたアルゴリズムを実行するための、サブルーチンを実装します。

import jijsolver
import numpy as np
import pandas_datareader.data as web
from scipy.stats import norm
import yfinance as yf

# a function to compute reference vlatility and expected shortfall
def compute_reference(ref_asset, ref_start_date, ref_end_date, alpha):
    ref_data = yf.download(ref_asset, ref_start_date, ref_end_date)['Close']
    ref_volatility = ref_data.std()
    ref_mean = ref_data.mean()
    ref_expected_shortfall = ref_mean + ref_volatility * norm.pdf(norm.ppf(alpha)) / (1-alpha)
    return ref_volatility[ref_asset], ref_expected_shortfall[ref_asset]

# a function to extract a solution from results
def extract_solution(solution, N):
    df = solution.decision_variables
    list_index = np.ravel(df[df["value"]==1]["subscripts"].to_list())
    list_binary = np.zeros(N, dtype=int)
    list_binary[list_index] = 1
    return list_binary

# solve Markowitz optimization problem
def solve_markowitz(instance_data):
    # compute using JijSolver
    interpreter = jm.Interpreter(instance_data)
    instance = interpreter.eval_problem(problem)
    solution = jijsolver.solve(instance, time_limit_sec=1.0)
    w_t = extract_solution(solution, instance_data['N'])
    return w_t

# compute expected shortfall from result of Markowitz optimization
def compute_shortfall(alpha, w, ret, vol):
    es = np.dot(w, ret) + np.dot(w, vol) * norm.pdf(norm.ppf(alpha)) / (1-alpha)
    return es
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[4], line 3
      1 import jijsolver
      2 import numpy as np
----> 3 import pandas_datareader.data as web
      4 from scipy.stats import norm
      5 import yfinance as yf

ModuleNotFoundError: No module named 'pandas_datareader'

compute_referenceは参照データのボラティリティ\(\sigma_\mathrm{ref}\)と期待ショートフォール\(\mathrm{ES}_\mathrm{ref}\)を計算する関数、solve_markowitzは(3)式の最適化を解く関数です。 extract_solutionsolve_markowitzの結果から目的関数が最小かつ実行可能解であるものを抽出し、それをバイナリのベクトルに変換する関数です。 最後に、compute_shortfallsolve_markowitzから得られたポートフォリオを用いて、期待ショートフォールを計算します。 これらのサブルーチンを用いて、Xu et al. 2023で提案されたアルゴリズムを実装すると以下のようになります。

from datetime import datetime
from datetime import timedelta
import pandas as pd
import pandas_datareader.data as web

# set input parameters, alpha, delta and eps
alpha = 0.99
delta = 0.1
eps = 0.25
# set start and end date for reference data
ref_start_date = datetime(year=2008, month=1, day=1)
ref_end_date = datetime(year=2009, month=1, day=1)
# compute volatility and expected shortfall from reference data
ref_volatility, ref_expected_shortfall = compute_reference('SPY', ref_start_date, ref_end_date, alpha)
# set the name of assets
list_assets = ['SPY', 'EEM', 'QQQ', 'SLV', 'SQQQ', 'XLF', 
                'AUDUSD=X', 'EURUSD=X', 'GBPUSD=X', 'CNYUSD=X', 'INRUSD=X', 'JPYUSD=X']
ref_asset_t = list_assets[0]
# set start date for data and date interval
start_date_t = datetime(year=2022, month=1, day=1)
dt = 14
# set the number of iteration
iteration = 6
# initialize lists for final results
list_results = []
list_shortfall = []
list_return = []
list_target = []
# main loop
for _ in range(iteration):
    # set end date
    end_date_t = start_date_t + timedelta(days=dt)
    # get reference stock data
    ref_data_for_volatility_t = yf.download(ref_asset_t, start_date_t, end_date_t)['Close']
    # compute volatility
    ref_volatility_t = ref_data_for_volatility_t.std()[ref_asset_t]
    # compute target expected shortfall
    target_expected_shortfall_t = ref_volatility / ref_volatility_t * ref_expected_shortfall
    # initialize dataframe
    data_assets = pd.DataFrame()
    for asset in list_assets:
        # get stock data and concat 
        data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
        data_assets = pd.concat([data_assets, data[asset]], axis=1)
    # if nan exists, drop
    data_assets_dropna = data_assets.dropna(how='any')
    # compute mean
    mean_t = data_assets_dropna.mean(axis=0).values
    # compute covariant matrix
    cov_t = data_assets_dropna.cov().values
    # compute volatility
    volatility_t = data_assets.std().values
    # get the number of assets
    N = len(list_assets)
    # normalize mean, covariant matrix, and pt
    mean_t_normalize = mean_t / mean_t.max()
    cov_t_normalize = cov_t / cov_t.max()
    p_t_normalize = mean_t_normalize.mean()
    instance_data = {'N': N, 'p': p_t_normalize, 'mu': mean_t_normalize, 'C': cov_t_normalize}
    while True:
        w_t = solve_markowitz(instance_data)
        if p_t_normalize > max(mean_t_normalize):
            print('Caution!!! Return constraint cannot be satisfied!!!')
            break
        expected_shortfall_from_markowitz = compute_shortfall(alpha, w_t, mean_t, volatility_t)
        if abs(expected_shortfall_from_markowitz) / abs(target_expected_shortfall_t) > 1.0 + eps:
            instance_data['p'] *= 1.0 - delta
        # elif abs(expected_shortfall_from_markowitz) / abs(target_expected_shortfall_t) < 1 - eps:
        #     instance_data['p'] *= 1 + delta
        else:
            break
    start_date_t = end_date_t
    list_results.append(w_t)
    list_shortfall.append(expected_shortfall_from_markowitz)
    list_target.append(target_expected_shortfall_t)
    list_return.append(mean_t)
/tmp/ipykernel_177/1442432888.py:9: FutureWarning: YF.download() has changed argument auto_adjust default to True
  ref_data = yf.download(ref_asset, ref_start_date, ref_end_date)['Close']
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:34: FutureWarning: YF.download() has changed argument auto_adjust default to True
  ref_data_for_volatility_t = yf.download(ref_asset_t, start_date_t, end_date_t)['Close']
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:34: FutureWarning: YF.download() has changed argument auto_adjust default to True
  ref_data_for_volatility_t = yf.download(ref_asset_t, start_date_t, end_date_t)['Close']
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:34: FutureWarning: YF.download() has changed argument auto_adjust default to True
  ref_data_for_volatility_t = yf.download(ref_asset_t, start_date_t, end_date_t)['Close']
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:34: FutureWarning: YF.download() has changed argument auto_adjust default to True
  ref_data_for_volatility_t = yf.download(ref_asset_t, start_date_t, end_date_t)['Close']
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:34: FutureWarning: YF.download() has changed argument auto_adjust default to True
  ref_data_for_volatility_t = yf.download(ref_asset_t, start_date_t, end_date_t)['Close']
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:34: FutureWarning: YF.download() has changed argument auto_adjust default to True
  ref_data_for_volatility_t = yf.download(ref_asset_t, start_date_t, end_date_t)['Close']
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed
/tmp/ipykernel_177/171787468.py:43: FutureWarning: YF.download() has changed argument auto_adjust default to True
  data = yf.download(asset, start_date_t, end_date_t).rename(columns={'Close': asset})
[*********************100%***********************]  1 of 1 completed

結果の可視化#

先程得た結果を可視化してみましょう。

import matplotlib.pyplot as plt

list_final_ret = [np.dot(x, y) for x, y in zip(list_results, list_return)]
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.set_xlabel('Period')
ax1.set_ylabel('Return')
ln1 = ax1.plot(list_final_ret, color='green', label='Return')
ax2 = ax1.twinx()
ax2.set_ylabel('Shortfall')
ln2 = ax2.plot(list_shortfall, color='indigo', label='Expected shortfall')
ln3 = ax2.plot(list_target, color='dodgerblue', label='Target shortfall')
h1, l1 = ax1.get_legend_handles_labels()
h2, l2 = ax2.get_legend_handles_labels()
ax1.legend(h1+h2, l1+l2, loc='upper right')
<matplotlib.legend.Legend at 0x7dc1fe5af390>
../_images/31a3241b28c2b0aebdafc8ecdf122db43030afd2c659e312d9362ffd88a25eb9.png

一番最後のperiodでは、目標とするショートフォール(target shortfall)が減少しています。 それに合わせて期待ショートフォールが下がるようにポートフォリオを選んだ結果、得られるリターンが小さくなっています。

参考文献#