効率的フロンティアによるポートフォリオ最適化 in Python
このプログラムは効率的フロンティアを計算し、リスクとリターンのトレードオフを考慮した上で最適なポートフォリオ配分を提示します。プログラムは以下を行います。
特徴
ユーザーから入力を受け取り(ティッカーシンボル、期間、リスクフリーレート)
指定された期間のデータを取得 (yfinanceを利用)
リターンを計算
平均リターンと共分散行列を計算
接点ポートフォリオと最小分散ポートフォリオを特定
効率的フロンティアを可視化
要件
プログラムを実行するには以下のライブラリが必要です。
numpy
pandas
yfinance
matplotlib
scipy
これらのライブラリは、pipを使用してインストールできます。
pip install numpy pandas yfinance matplotlib scipy
使用方法
1. プログラムを好きな場所に保存します。
2. コマンドプロンプトからプログラムを実行します。
python portfolio_optimizer.py
3. プログラムの指示に従って、以下の情報を入力します:
ティッカーシンボル(カンマ区切りで複数入力可能)
分析開始日(YYYY-MM-DD形式)
分析終了日(YYYY-MM-DD形式)
リスクフリーレート(百分率で入力)
スクリプト詳細
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from scipy.optimize import minimize
from datetime import datetime
def get_data(tickers, start_date, end_date):
data = yf.download(tickers, start=start_date, end=end_date)['Adj Close']
returns = data.pct_change().dropna()
return returns
def portfolio_annualized_performance(weights, mean_returns, cov_matrix):
returns = np.sum(mean_returns * weights) * 252
std = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) * np.sqrt(252)
return std, returns
def negative_sharpe_ratio(weights, mean_returns, cov_matrix, risk_free_rate):
p_var, p_ret = portfolio_annualized_performance(weights, mean_returns, cov_matrix)
return -(p_ret - risk_free_rate) / p_var
def tangency_portfolio(mean_returns, cov_matrix, risk_free_rate):
num_assets = len(mean_returns)
args = (mean_returns, cov_matrix, risk_free_rate)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bound = (0.0, 1.0)
bounds = tuple(bound for asset in range(num_assets))
result = minimize(negative_sharpe_ratio, num_assets*[1./num_assets,], args=args,
method='SLSQP', bounds=bounds, constraints=constraints)
return result
def portfolio_volatility(weights, mean_returns, cov_matrix):
return portfolio_annualized_performance(weights, mean_returns, cov_matrix)[0]
def min_variance_portfolio(mean_returns, cov_matrix):
num_assets = len(mean_returns)
args = (mean_returns, cov_matrix)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bound = (0.0, 1.0)
bounds = tuple(bound for asset in range(num_assets))
result = minimize(portfolio_volatility, num_assets*[1./num_assets,], args=args,
method='SLSQP', bounds=bounds, constraints=constraints)
return result
def efficient_frontier(mean_returns, cov_matrix, returns_range):
efficients = []
for ret in returns_range:
constraints = ({'type': 'eq', 'fun': lambda x: portfolio_annualized_performance(x, mean_returns, cov_matrix)[1] - ret},
{'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
result = minimize(portfolio_volatility, len(mean_returns)*[1./len(mean_returns),], args=(mean_returns, cov_matrix),
method='SLSQP', bounds=tuple((0,1) for _ in range(len(mean_returns))), constraints=constraints)
efficients.append(result['fun'])
return efficients
def display_efficient_frontier(mean_returns, cov_matrix, num_portfolios, risk_free_rate, tickers, start_date, end_date):
returns = []
volatilities = []
for _ in range(num_portfolios):
weights = np.random.random(len(mean_returns))
weights /= np.sum(weights)
returns.append(np.sum(mean_returns * weights) * 252)
volatilities.append(np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) * np.sqrt(252))
max_sharpe = tangency_portfolio(mean_returns, cov_matrix, risk_free_rate)
sdp, rp = portfolio_annualized_performance(max_sharpe['x'], mean_returns, cov_matrix)
max_sharpe_allocation = pd.DataFrame(max_sharpe['x'], index=mean_returns.index, columns=['allocation'])
max_sharpe_allocation.allocation = [round(i*100,2)for i in max_sharpe_allocation.allocation]
max_sharpe_allocation = max_sharpe_allocation.T
min_vari = min_variance_portfolio(mean_returns, cov_matrix)
sdp_min, rp_min = portfolio_annualized_performance(min_vari['x'], mean_returns, cov_matrix)
min_vari_allocation = pd.DataFrame(min_vari['x'], index=mean_returns.index, columns=['allocation'])
min_vari_allocation.allocation = [round(i*100,2)for i in min_vari_allocation.allocation]
min_vari_allocation = min_vari_allocation.T
print("-"*80)
print("Tangency Portfolio Allocation\n")
print("Annualized Return: {:.2f}%".format(rp * 100))
print("Annualized Volatility: {:.2f}%".format(sdp * 100))
print("Sharpe Ratio: {:.2f}".format((rp - risk_free_rate) / sdp))
print("\n")
print(max_sharpe_allocation)
print("-"*80)
print("Minimum Variance Portfolio Allocation\n")
print("Annualized Return: {:.2f}%".format(rp_min * 100))
print("Annualized Volatility: {:.2f}%".format(sdp_min * 100))
print("Sharpe Ratio: {:.2f}".format((rp_min - risk_free_rate) / sdp_min))
print("\n")
print(min_vari_allocation)
plt.figure(figsize=(12, 8))
scatter = plt.scatter(volatilities, returns, c=(np.array(returns)-risk_free_rate)/np.array(volatilities), cmap='YlGnBu', marker='o', s=10, alpha=0.3)
plt.colorbar(scatter, label='Sharpe Ratio')
plt.scatter(sdp, rp, marker='*', color='r', s=500, label='Tangency Portfolio')
plt.scatter(sdp_min, rp_min, marker='*', color='g', s=500, label='Minimum Variance Portfolio')
target = np.linspace(rp_min, max(returns), 50)
efficient_portfolios = efficient_frontier(mean_returns, cov_matrix, target)
plt.plot(efficient_portfolios, target, linestyle='-.', color='black', label='Efficient Frontier')
plt.title('Portfolio Optimization with the Efficient Frontier')
plt.xlabel('Annualized Volatility')
plt.ylabel('Annualized Returns')
plt.legend(labelspacing=0.8)
# Add text box with additional information
info_text = f"Tickers: {', '.join(tickers)}\n"
info_text += f"Period: {start_date} to {end_date}\n"
info_text += f"Risk-free Rate: {risk_free_rate:.2%}"
plt.text(0.05, 0.05, info_text, transform=plt.gca().transAxes,
bbox=dict(facecolor='white', alpha=0.8), fontsize=9)
plt.tight_layout()
plt.show()
def get_user_input():
# ティッカーシンボルの入力
while True:
tickers_input = input("ティッカーシンボルをカンマ区切りで入力してください(例: VT,EDV,GLDM): ")
tickers = [ticker.strip() for ticker in tickers_input.split(',')]
if len(tickers) > 1:
break
else:
print("少なくとも2つ以上のティッカーシンボルを入力してください。")
# 日付の入力と検証
def get_valid_date(prompt):
while True:
date_str = input(prompt)
try:
return datetime.strptime(date_str, "%Y-%m-%d").strftime("%Y-%m-%d")
except ValueError:
print("無効な日付形式です。YYYY-MM-DDの形式で入力してください。")
start_date = get_valid_date("開始日を入力してください(YYYY-MM-DD): ")
end_date = get_valid_date("終了日を入力してください(YYYY-MM-DD): ")
# リスクフリーレートの入力
while True:
try:
risk_free_rate = float(input("リスクフリーレートを百分率で入力してください(例: 1.0): ")) / 100
break
except ValueError:
print("無効な入力です。数値を入力してください。")
return tickers, start_date, end_date, risk_free_rate
def main():
print("ポートフォリオ最適化プログラムへようこそ!")
print("このプログラムは効率的フロンティアを計算し、最適なポートフォリオ配分を提示します。\n")
tickers, start_date, end_date, risk_free_rate = get_user_input()
print("\n計算を開始します...")
# データの取得
returns = get_data(tickers, start_date, end_date)
# 平均リターンと共分散行列の計算
mean_returns = returns.mean()
cov_matrix = returns.cov()
# 効率的フロンティアの表示
display_efficient_frontier(mean_returns, cov_matrix, num_portfolios=25000, risk_free_rate=risk_free_rate,
tickers=tickers, start_date=start_date, end_date=end_date)
if __name__ == "__main__":
main()
出力
プログラムは以下のような出力をコンソールに表示します。
接点ポートフォリオの資産配分、年率リターン、年率ボラティリティ、シャープレシオ
最小分散ポートフォリオの資産配分、年率リターン、年率ボラティリティ、シャープレシオ
効率的フロンティアのグラフ
ポートフォリオ最適化プログラムへようこそ!
このプログラムは効率的フロンティアを計算し、最適なポートフォリオ配分を提示します。
ティッカーシンボルをカンマ区切りで入力してください(例: VT,EDV,GLDM): VT,EDV,GLDM
開始日を入力してください(YYYY-MM-DD): 2010-01-01
終了日を入力してください(YYYY-MM-DD): 2023-12-31
リスクフリーレートを百分率で入力してください(例: 1.0): 1
計算を開始します...
[*********************100%%**********************] 3 of 3 completed
--------------------------------------------------------------------------------
Tangency Portfolio Allocation
Annualized Return: 10.02%
Annualized Volatility: 12.41%
Sharpe Ratio: 0.73
Ticker EDV GLDM VT
allocation 0.0 66.19 33.81
--------------------------------------------------------------------------------
Minimum Variance Portfolio Allocation
Annualized Return: 7.74%
Annualized Volatility: 11.38%
Sharpe Ratio: 0.59
Ticker EDV GLDM VT
allocation 22.36 45.07 32.57
グラフには以下の情報が含まれます。
様々なポートフォリオ配分(散布図)
接点ポートフォリオ(赤い星印)
最小分散ポートフォリオ(緑の星印)
効率的フロンティア(黒い点線)
分析対象のティッカー、期間、リスクフリーレートの情報
注意事項
このスクリプトは情報提供の目的でのみ作成されています。金融アドバイスを構成するものではありません。