見出し画像

効率的フロンティアによるポートフォリオ最適化 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

グラフには以下の情報が含まれます。

  • 様々なポートフォリオ配分(散布図)

  • 接点ポートフォリオ(赤い星印)

  • 最小分散ポートフォリオ(緑の星印)

  • 効率的フロンティア(黒い点線)

  • 分析対象のティッカー、期間、リスクフリーレートの情報

注意事項

このスクリプトは情報提供の目的でのみ作成されています。金融アドバイスを構成するものではありません。

いいなと思ったら応援しよう!