Pythonでポートフォリオのシャープレシオを最大化する|PyPortfolioOpt入門【製造メーカー株で実践】

子供が生まれてから「なんとなく感覚で銘柄を買う」時間的余裕がなくなって、せめて保有銘柄の比率くらいはちゃんと考えたいと思いはじめました。トヨタ多めで持ってるけど、本当にこの配分でいいのか? → そこで「シャープレシオを最大化する最適な配分」をPythonで計算する方法を調べました。

なぜポートフォリオ最適化を調べたか

僕のポートフォリオは「なんとなくトヨタが多め、あとはデンソーとダイキンを少し」みたいな感じでした。根拠がないわけじゃないけど、数値で検証したことはなかった。

「シャープレシオ」とは、リスク(標準偏差)あたりのリターンを示す指標です。これを最大化するような資産配分を求めるのが、モダンポートフォリオ理論(MPT)の考え方。古典的な理論ですが、個人投資家でもPythonで実装できるのを知って試してみました。

PyPortfolioOptとは

PyPortfolioOptは、ポートフォリオ最適化のためのPythonライブラリです。期待リターンの計算、リスクモデルの推定、最適化(シャープレシオ最大化・最小分散など)を数行のコードで実行できます。

pip install PyPortfolioOpt yfinance

実践:製造メーカー4銘柄で最適配分を求める

トヨタ(7203.T)、デンソー(6902.T)、三菱重工(7011.T)、ダイキン(6367.T)の4銘柄を使います。

ステップ1:株価データ取得

import yfinance as yf
import pandas as pd
from pypfopt import EfficientFrontier, risk_models, expected_returns

# 製造メーカー銘柄(東証ティッカー)
tickers = ["7203.T", "6902.T", "7011.T", "6367.T"]
labels  = ["トヨタ", "デンソー", "三菱重工", "ダイキン"]

# 過去2年の週次終値
raw = yf.download(tickers, period="2y", interval="1wk", auto_adjust=True)["Close"]
raw.columns = labels
prices = raw.dropna()

print(prices.tail(3))

ステップ2:期待リターンとリスクモデルの推定

# 期待リターン(過去データから年率換算の平均リターンを推定)
mu = expected_returns.mean_historical_return(prices, frequency=52)  # 52週

# リスクモデル(Ledoit-Wolf法による共分散行列)
S = risk_models.CovarianceShrinkage(prices, frequency=52).ledoit_wolf()

print("期待リターン(年率):")
for name, r in zip(labels, mu):
    print(f"  {name}: {r:.1%}")

frequency=52 は週次データを年率換算するための指定です。共分散行列には Ledoit-Wolf法を使って推定誤差を抑えています(データが少ないときに有効)。

ステップ3:シャープレシオ最大化

# 効率的フロンティアの計算
ef = EfficientFrontier(mu, S)

# シャープレシオ最大化(リスクフリーレート = 0.5%程度を想定)
ef.max_sharpe(risk_free_rate=0.005)

# 最適ウェイト(小数点以下を丸める)
weights = ef.clean_weights()
print("\n最適配分(シャープレシオ最大):")
for name, w in zip(labels, weights.values()):
    print(f"  {name}: {w:.1%}")

# パフォーマンス指標
perf = ef.portfolio_performance(verbose=True, risk_free_rate=0.005)

出力例(データ時点により変わります):

最適配分(シャープレシオ最大):
  トヨタ: 12.3%
  デンソー: 5.8%
  三菱重工: 58.1%
  ダイキン: 23.8%

Expected annual return: 24.7%
Annual volatility: 18.2%
Sharpe Ratio: 1.31

僕が実際に試したとき、三菱重工の比率が高くなりました。防衛・エネルギー需要で株価が強い時期だったのでこうなっています。このあたりは「過去データへの過学習」になりうるので注意が必要です。

効率的フロンティアを描く

リターンとリスクのトレードオフを可視化してみます:

import numpy as np
import matplotlib.pyplot as plt
from pypfopt.plotting import plot_efficient_frontier

fig, ax = plt.subplots(figsize=(9, 6))

# モンテカルロ法でランダムポートフォリオを散布
num_portfolios = 3000
results = np.zeros((3, num_portfolios))
for i in range(num_portfolios):
    w = np.random.dirichlet(np.ones(len(labels)))
    port_return = np.dot(w, mu.values)
    port_vol = np.sqrt(np.dot(w.T, np.dot(S.values, w)))
    results[0, i] = port_vol
    results[1, i] = port_return
    results[2, i] = port_return / port_vol  # シャープレシオ近似

sc = ax.scatter(results[0], results[1], c=results[2], cmap="viridis", alpha=0.4, s=10)
plt.colorbar(sc, ax=ax, label="シャープレシオ")
ax.set_xlabel("リスク(年率)")
ax.set_ylabel("期待リターン(年率)")
ax.set_title("製造メーカー4銘柄 効率的フロンティア")
plt.tight_layout()
plt.savefig("efficient_frontier.png", dpi=150)
plt.show()

注意点:最適化の落とし穴

過去データから求めた最適配分が、将来も最適とは限りません。特に:

  • 過学習リスク:データ期間が短いと、たまたま過去に強かった銘柄に偏る
  • コーナー解:一部銘柄に100%近い比率が集中することがある(上限制約で抑制可)
  • 取引コスト無視:リバランスのたびに手数料がかかる点は考慮外

ウェイトに上限を設けるなら ef.add_constraint(lambda x: x <= 0.4) で各銘柄40%上限にできます。

まとめ

PyPortfolioOptを使えば、「なんとなく多めに持ってる」配分を数値で見直すことができます。過去データに依存するという限界はありますが、ゼロよりはずっとマシな意思決定ができると思う。

個人的には、このシャープレシオ最大化の結果と、自分の現状の配分を比べて愕然としました(笑)。次は「リスク上限を設けた最小分散ポートフォリオ」と比べてみたいと思っています。

タイトルとURLをコピーしました