Skip to content

Instantly share code, notes, and snippets.

@Polyterative
Last active May 12, 2023 21:31
Show Gist options
  • Save Polyterative/de84eb653abdc06044a8e701ae0b30b2 to your computer and use it in GitHub Desktop.
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.
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