K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

投資信託

05.4.1

投資信託

最終更新 2020-01-03 読了時間 4 分
まとめ
効率的フロンティアは、複数の資産を組み合わせたときに「同じリスク(価格の揺れ)で最も高いリターンが得られる配分」を結んだ曲線で、ポートフォリオ理論の中心にある考え方です。このページでは WEALTH ADVISER から保存した 5 本の投資信託(全世界株式、TOPIX、S&P 500 連動 2 本、純金)の週次基準価額を pd.merge で 1 つの表にし、pypfopt の mean_historical_return で期待リターン、sample_cov で共分散行列を求め、EfficientFrontier(mu, S).max_sharpe() でシャープレシオ最大の配分を計算します。CLA と plotting.plot_efficient_frontier で曲線と各ファンドの位置を描き、最後に diff().corr() のヒートマップで純金が株式ファンドと相関が低いことを確かめます。読み終えると、過去の価格系列から「期待リターン・共分散 → 最適配分」を出す手順が 10 行程度で書けるようになります。

直感 #

株式ファンドを 4 本持っていても、どれも世界の株価と一緒に上下するなら「分散」にはなりません。値動きの相関が低い資産(ここでは相関 0.2 前後の純金)を混ぜると、片方が下がるときに他方が支えて全体の揺れが小さくなります。効率的フロンティアは、5 本の配分比率をあらゆる組み合わせで試し、「このリスクなら最大でこのリターン」という上限を曲線にしたものです。その曲線上で、リターンをリスクで割ったシャープレシオが最大になる 1 点が max_sharpe() の答えで、pypfopt は過去の価格から期待リターンと共分散を見積もり、その最適化を凸計画問題として解きます。

投資信託で効率的フロンティアを計算してみる #

比較に使うファンドは次の 5 本(コードは WEALTH ADVISER のファンド ID)です。全世界株式 2018103105(eMAXIS Slim 全世界株式オール・カントリー)、国内株式 2015042708(ニッセイ TOPIX インデックスファンド)、米国株式 2019092601(SBI・V・S&P500 インデックス・ファンド)と 2018070301(eMAXIS Slim 米国株式 S&P500)、そして株式と値動きの異なる参考として 2011020701(三菱UFJ 純金ファンド)を加えています。各ファンドのページは https://www.wealthadvisor.co.jp/FundData/SnapShot.do?fnc=<ファンドID> で開けます。

データの読み込み #

WEALTH ADVISER から手動で保存した週次の基準価額 CSV を、ファンド ID ごとのフォルダから読み込みます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import os

import pandas as pd

データ保存先ディレクトリ = "C://Users//nanja-win-ms//Dropbox//PC//Downloads//"
投資信託一覧 = {
    "2018103105": "eMAXIS Slim全世界株式(オール・カントリー)",
    "2015042708": "ニッセイ TOPIXインデックスファンド",
    "2019092601": "SBI・V・S&P500インデックス・ファンド",
    "2018070301": "eMAXIS Slim米国株式(S&P500)",
    "2011020701": "三菱UFJ 純金ファンド",
}

投資信託リターン = {}

for 投資信託ID, 投資信託名 in 投資信託一覧.items():
    月次リターンファイル名 = [
        c
        for c in os.listdir(os.path.join(データ保存先ディレクトリ, 投資信託ID))
        if c.startswith("基準価額")
    ][0]
    投資信託リターン[投資信託ID] = pd.read_csv(
        os.path.join(データ保存先ディレクトリ, 投資信託ID, 月次リターンファイル名), encoding="cp932"
    )
    投資信託リターン[投資信託ID].columns = ["日付", 投資信託名]

データの整形 #

行 = 日付、列 = ファンド名の 1 つの表に結合します。pypfopt はこの「価格の表」を入力にするので、投資信託でなく株式の終値でも同じ形にすれば使えます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ポートフォリオ = None

for 投資信託ID in 投資信託一覧.keys():
    if ポートフォリオ is None:
        ポートフォリオ = 投資信託リターン[投資信託ID]
    else:
        ポートフォリオ = pd.merge(ポートフォリオ, 投資信託リターン[投資信託ID], on="日付")

ポートフォリオ.index = pd.to_datetime(ポートフォリオ["日付"], format="%Y%m%d")
ポートフォリオ.drop("日付", axis=1, inplace=True)

ポートフォリオ

eMAXIS Slim全世界株式(オール・カントリー)ニッセイ TOPIXインデックスファンドSBI・V・S&P500インデックス・ファンドeMAXIS Slim米国株式(S&P500)三菱UFJ 純金ファンド
日付
2020-01-101182811720112031218513827
2020-01-171203611723114291242814063
2020-01-241193211689113811237814008
2020-01-311166711379111891216814085
2020-02-071198711703114901249614242
..................
2023-12-292089917484222292428123054
2024-01-052097217684222852434223270
2024-01-122128318428227632487123226
2024-01-192153618543232192536923562
2024-01-262191618450236992588823437

212 rows × 5 columns

可視化 #

5 本の基準価額を 1 枚に重ね、値動きの向きと大きさを目で確かめます。

1
2
3
4
5
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(12, 6))
sns.lineplot(data=ポートフォリオ)
<Axes: xlabel='日付'>

可視化

1
2
3
4
5
6
7
8
from pypfopt import expected_returns, risk_models
from pypfopt.efficient_frontier import EfficientFrontier

mu = expected_returns.mean_historical_return(ポートフォリオ)
S = risk_models.sample_cov(ポートフォリオ)
ef = EfficientFrontier(mu, S)
weights = ef.max_sharpe()
ef.portfolio_performance(verbose=True)
Expected annual return: 115.5%
Annual volatility: 34.2%
Sharpe Ratio: 3.32


(1.1546507705298557, 0.34188826020611157, 3.318776637272708)
1
2
3
4
5
6
7
8
from pypfopt import CLA, plotting

cla = CLA(mu, S)
cla.max_sharpe()
cla.portfolio_performance(verbose=True)

plt.figure(figsize=(10, 5))
plotting.plot_efficient_frontier(cla, show_assets=True, points=50, show_tickers=True)
Expected annual return: 115.1%
Annual volatility: 34.1%
Sharpe Ratio: 3.32


<Axes: xlabel='Volatility', ylabel='Return'>

可視化

実行結果の読み方 #

曲線の左下から右上へ、リスクを取るほどリターンが上がる効率的フロンティアが描かれ、各ファンドの位置が点で示されます。純金ファンドは左寄り(低リスク)、S&P 500 の 2 本は右上(高リスク・高リターン)に位置し、最大シャープの配分がこの間を結ぶ曲線上の 1 点として選ばれています。S&P 500 連動の 2 本はほぼ同じ場所に重なり、同じ指数に連動するファンドを 2 本持っても分散効果が無いことが点の重なりから分かります。

値動きの相関 #

基準価額そのものではなく週ごとの変化量 diff() で相関を計算し、どのファンド同士が一緒に動くかをヒートマップで見ます。

1
sns.heatmap(ポートフォリオ.diff().dropna().corr(), annot=True, fmt="1.4f")
<Axes: >

値動きの相関の図

使いどころと落とし穴 #

mean_historical_return は「過去 4 年の平均リターンが将来も続く」と仮定するので、2020〜2024 年の株高だけを入力にした年率 115% やシャープ 3.32 は、そのまま将来の期待値として受け取れません。期間を 10 年以上にする、expected_returns.ema_historical_return で直近に重みを付ける、risk_models.CovarianceShrinkage(prices).ledoit_wolf() で共分散の推定誤差を抑える、といった選択肢を比べるのが実務的です。max_sharpe() は入力のわずかな違いで配分が大きく変わる(1 本に 100% 寄ることも多い)ため、ef.add_objective(objective_functions.L2_reg) で配分を分散させる正則化を加えるか、weight_bounds=(0, 0.4) で 1 本の上限を決めると安定します。pd.merge の内部結合で期間が最も短いファンドに揃うため、設定日の新しいファンドを混ぜるとデータが数年分しか残らない点にも注意してください。

まとめ #

効率的フロンティアの計算は、価格の表から mean_historical_return で期待リターン、sample_cov で共分散を求め、EfficientFrontier(mu, S).max_sharpe() に渡すだけで配分と年率リターン・ボラティリティ・シャープレシオが出ます。CLA と plotting.plot_efficient_frontier で曲線を描けば、単独のファンドがすべて曲線の内側にあること、純金のような相関の低い資産が曲線を左に押し出していることが見えます。年率 115% のような数値は入力期間の相場をそのまま反映するので、期間や ledoit_wolf などの推定方法を変えて配分がどれだけ動くかを確認してから使ってください。