マーコウィッツのポートフォリオ理論は、複数の資産を組み合わせてリターンを最大化しながらリスクを最小化するフレームワークです。ということで、この記事ではPythonで効率的フロンティアを計算し、最適ポートフォリオのウェイトを求める実装をまとめます。
📘 外部参考:Python 公式(ダウンロード) / Python 公式ドキュメント(日本語)
📘 外部参考:Efficient Frontier(Investopedia)
📘 外部参考:現代ポートフォリオ理論(Wikipedia 日本語) / Modern Portfolio Theory(Investopedia)
ライブラリのインストールとデータ取得
pip install yfinance pandas numpy scipy matplotlib
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from scipy.optimize import minimize
# 日本株ポートフォリオ候補
tickers = ['7203.T', '6758.T', '9984.T', '4063.T', '6861.T']
names = ['トヨタ', 'ソニー', 'ソフトバンクG', '信越化学', 'キーエンス']
def get_returns(tickers, period='3y'):
data = yf.download(tickers, period=period, progress=False)['Close']
returns = data.pct_change().dropna()
return returns
returns = get_returns(tickers)
print("年率リターン:")
print((returns.mean() * 252).to_string())
print("\n年率ボラティリティ:")
print((returns.std() * np.sqrt(252)).to_string())
ポートフォリオのリターン・リスク計算
def portfolio_stats(weights, returns):
weights = np.array(weights)
ann_return = np.sum(returns.mean() * weights) * 252
ann_vol = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
sharpe = ann_return / ann_vol
return ann_return, ann_vol, sharpe
# 等加重ポートフォリオの確認
n = len(tickers)
equal_weights = np.array([1/n] * n)
ret, vol, sharpe = portfolio_stats(equal_weights, returns)
print(f"等加重ポートフォリオ")
print(f" 期待リターン: {ret:.2%}")
print(f" リスク(標準偏差): {vol:.2%}")
print(f" シャープレシオ: {sharpe:.2f}")
効率的フロンティアの計算
def efficient_frontier(returns, n_points=200):
n = len(returns.columns)
results = []
for _ in range(n_points):
# ランダムウェイトを生成して効率的フロンティアをモンテカルロでプロット
w = np.random.dirichlet(np.ones(n))
ret, vol, sharpe = portfolio_stats(w, returns)
results.append({'return': ret, 'vol': vol, 'sharpe': sharpe, 'weights': w})
return pd.DataFrame(results)
def find_optimal_portfolio(returns, objective='sharpe'):
n = len(returns.columns)
constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
bounds = [(0.05, 0.4)] * n # 各銘柄5%〜40%に制限
initial = np.array([1/n] * n)
if objective == 'sharpe':
def neg_sharpe(w):
return -portfolio_stats(w, returns)[2]
result = minimize(neg_sharpe, initial, bounds=bounds, constraints=constraints)
elif objective == 'min_vol':
def portfolio_vol(w):
return portfolio_stats(w, returns)[1]
result = minimize(portfolio_vol, initial, bounds=bounds, constraints=constraints)
opt_weights = result.x
opt_ret, opt_vol, opt_sharpe = portfolio_stats(opt_weights, returns)
return opt_weights, opt_ret, opt_vol, opt_sharpe
# 最大シャープレシオポートフォリオ
opt_w, opt_ret, opt_vol, opt_sharpe = find_optimal_portfolio(returns, 'sharpe')
print("最大シャープレシオ ポートフォリオ")
for ticker, name, w in zip(tickers, names, opt_w):
print(f" {name}({ticker}): {w:.1%}")
print(f" 期待リターン: {opt_ret:.2%}")
print(f" リスク: {opt_vol:.2%}")
print(f" シャープレシオ: {opt_sharpe:.2f}")
リバランシング戦略の実装
def rebalancing_backtest(returns, rebalance_freq='Q', objective='sharpe'):
"""
定期リバランシング戦略のバックテスト
rebalance_freq: 'M'=毎月, 'Q'=四半期, 'Y'=年次
"""
portfolio_value = 1.0
current_weights = np.array([1/len(returns.columns)] * len(returns.columns))
history = []
rebalance_dates = pd.date_range(returns.index[60], returns.index[-1], freq=rebalance_freq)
for date in rebalance_dates:
# この時点までのデータで最適化
hist_data = returns[returns.index <= date].tail(252)
if len(hist_data) < 60:
continue
try:
opt_w, _, _, _ = find_optimal_portfolio(hist_data, objective)
current_weights = opt_w
except Exception:
pass
# 次のリバランスまでのリターンを計算
next_dates = returns[(returns.index > date)].index
if len(next_dates) == 0:
break
next_rebalance = rebalance_dates[rebalance_dates > date][0] if any(rebalance_dates > date) else returns.index[-1]
period_returns = returns[(returns.index > date) & (returns.index <= next_rebalance)]
if len(period_returns) > 0:
period_portfolio_return = (period_returns * current_weights).sum(axis=1).add(1).prod() - 1
portfolio_value *= (1 + period_portfolio_return)
history.append({'date': next_rebalance, 'value': portfolio_value})
total_return = portfolio_value - 1
print(f"リバランシング({rebalance_freq})戦略 総リターン: {total_return:.2%}")
return pd.DataFrame(history)
result_q = rebalancing_backtest(returns, 'Q')
result_m = rebalancing_backtest(returns, 'M')
まとめ
Pythonによるマーコウィッツのポートフォリオ最適化のポイントをまとめます。分散投資の効果は銘柄間の相関係数に依存し、低相関な銘柄の組み合わせがリスク低減に有効です。シャープレシオ最大化の最適化は、scipyのminimizeで制約付き非線形最適化として解けます。銘柄数が増えると共分散行列の推定誤差が大きくなるため、最小分散ポートフォリオや制約付き最適化が実用的です。定期的なリバランシング(四半期ごと等)で、市場変動によるウェイトのズレを修正することが長期運用では重要です。過去データで最適化されたウェイトが将来も最適とは限らないため、ロバスト最適化や正則化手法の併用を検討することをお勧めします。
📘 外部参考:シャープ・レシオ(Wikipedia) / Investopedia

