Last active
May 12, 2023 21:31
-
-
Save Polyterative/de84eb653abdc06044a8e701ae0b30b2 to your computer and use it in GitHub Desktop.
Portfolio Optimization & Analysis: Python script using historical data to optimize ETF portfolio allocation. Evaluate returns, risk, and Sharpe ratio. Personalize inputs for custom analysis.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import pandas as pd | |
import yfinance as yf | |
etfs = ['SWDA.MI', 'AGGH.MI', 'VECP.MI', 'VGEA.MI', 'RENW.MI', 'EIMI.MI', 'DRVE.MI'] | |
portfolio_amounts = [58.91, 3.74, 12.58, 12.01, 2.89, 7.21, 2.71] | |
annual_fees = [0.20, 0.10, 0.09, 0.07, 0.49, 0.18, 0.10] | |
risk_free_rate = 0.02 | |
total_investment = sum(portfolio_amounts) | |
start_date = '2015-01-01' | |
end_date = '2023-05-01' | |
data = yf.download(etfs, start=start_date, end=end_date)['Adj Close'] | |
returns = data.pct_change().dropna() | |
mean_returns = returns.mean() | |
cov_matrix = returns.cov() | |
def calculate_portfolio_stats(weights): | |
portfolio_return = np.sum(mean_returns * weights) * 252 | |
portfolio_std_dev = np.sqrt(weights.T @ cov_matrix @ weights) * np.sqrt(252) | |
portfolio_sharpe_ratio = (portfolio_return - risk_free_rate) / portfolio_std_dev | |
return portfolio_return, portfolio_std_dev, portfolio_sharpe_ratio | |
def optimize_portfolio(): | |
best_sharpe_ratio = -np.inf | |
best_allocation = None | |
combinations = np.random.rand(10000, len(etfs)) | |
combinations /= combinations.sum(axis=1, keepdims=True) | |
for combination in combinations: | |
portfolio_return, portfolio_std_dev, portfolio_sharpe_ratio = calculate_portfolio_stats(combination) | |
if portfolio_sharpe_ratio > best_sharpe_ratio: | |
best_sharpe_ratio = portfolio_sharpe_ratio | |
best_allocation = combination | |
adjusted_allocation = best_allocation * (1 - np.array(annual_fees)) | |
best_portfolio_return, best_portfolio_std_dev, _ = calculate_portfolio_stats(adjusted_allocation) | |
return best_allocation, best_portfolio_return, best_portfolio_std_dev | |
def create_allocation_table(allocation): | |
allocation_table = pd.DataFrame({'ETF': etfs, 'Allocation': allocation}) | |
allocation_table['Allocation (%)'] = allocation_table['Allocation'] * 100 | |
allocation_table = allocation_table[['ETF', 'Allocation (%)']] | |
allocation_table = allocation_table.round(2) | |
return allocation_table | |
def calculate_proposed_stats(): | |
portfolio_weights = np.array(portfolio_amounts) / total_investment | |
your_portfolio_return, your_portfolio_std_dev, _ = calculate_portfolio_stats(portfolio_weights) | |
return your_portfolio_return, your_portfolio_std_dev | |
import matplotlib.pyplot as plt | |
def plot_efficient_frontier(best_portfolio_std_dev, best_portfolio_return, your_portfolio_std_dev, | |
your_portfolio_return, risk_free_rate): | |
plt.figure(figsize=(10, 6)) | |
plt.scatter(best_portfolio_std_dev, best_portfolio_return, marker='o', color='red', s=200) | |
plt.scatter(your_portfolio_std_dev, your_portfolio_return, marker='o', color='green', s=200) | |
plt.scatter(0, risk_free_rate, marker='o', color='blue', s=200) | |
plt.plot([0, best_portfolio_std_dev], [risk_free_rate, best_portfolio_return], color='black', linestyle='-', | |
linewidth=2) | |
plt.plot([0, your_portfolio_std_dev], [risk_free_rate, your_portfolio_return], color='black', linestyle='-', | |
linewidth=2) | |
plt.title("Efficient Frontier") | |
plt.xlabel("Risk (Std. Deviation)") | |
plt.ylabel("Return") | |
plt.show() | |
def plot_portfolio_distribution(portfolio_amounts, labels, title): | |
plt.figure(figsize=(10, 6)) | |
plt.pie(portfolio_amounts, labels=labels, autopct='%1.1f%%') | |
plt.title(title) | |
plt.show() | |
# Optimization | |
best_allocation, best_portfolio_return, best_portfolio_std_dev = optimize_portfolio() | |
allocation_table = create_allocation_table(best_allocation) | |
# Proposed Portfolio | |
your_portfolio_return, your_portfolio_std_dev = calculate_proposed_stats() | |
# Print the results | |
print(allocation_table) | |
print() | |
print(f"Best Portfolio Return (adjusted for annual fees): {best_portfolio_return * 100:.2f}%") | |
print(f"Your Proposed Portfolio Return (adjusted for annual fees): {your_portfolio_return * 100:.2f}%") | |
print() | |
print(f"Best Portfolio Risk (Std. Deviation): {best_portfolio_std_dev * 100:.2f}%") | |
print(f"Your Proposed Portfolio Risk (Std. Deviation): {your_portfolio_std_dev * 100:.2f}%") | |
print() | |
print(f"Best Portfolio Sharpe Ratio: {(best_portfolio_return - risk_free_rate) / best_portfolio_std_dev:.2f}") | |
print(f"Your Proposed Portfolio Sharpe Ratio: {(your_portfolio_return - risk_free_rate) / your_portfolio_std_dev:.2f}") | |
print() | |
print(f"Worst ETF: {etfs[np.argmin(best_allocation)]}") | |
print(f"Best ETF: {etfs[np.argmax(best_allocation)]}") | |
# Plotting | |
plot_efficient_frontier(best_portfolio_std_dev, best_portfolio_return, your_portfolio_std_dev, your_portfolio_return, | |
risk_free_rate) | |
plot_portfolio_distribution(portfolio_amounts, etfs, "Portfolio Distribution") | |
plot_portfolio_distribution(best_allocation, etfs, "Optimized Portfolio Distribution") | |
plot_portfolio_distribution(best_allocation * (1 - np.array(annual_fees)), etfs, | |
"Optimized Portfolio Distribution (adjusted for annual fees)") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment