Portfolio Optimization
The optimization is based on historical data. The result doesn't mean it is the optimized portfolio for the future.I n fact, It's very likely that it isn't.
Start with imports (use pip install pandas-datareader if it's not installed):
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import pandas_datareader.data as pdr
Get selected coin historical data from Yahoo Finance (free). These three coin is specificly selected for this example after a few trial and error. They form a good portfolio optimization example. Clearly, ot all combinations of coins and dates will give similar results.
data = pdr.DataReader(['BTC-USD','ETH-USD','BNB-USD',], 'yahoo', start='2021-06-12', end='2021-12-30')
data = data.Close
dly_return = data.pct_change().iloc[1:]
The result is a simple dataframe with daily percentage returns:
Create randomly weighted portfolios and calculate their performances:
port_ret, port_std, port_weight, sharpe = [], [], [], []
num_asset = len(dly_return.columns)
num_port = 500
np.random.seed(101)
dly_cov = dly_return.cov()
for _ in range(num_port):
rand_weights = np.random.random(num_asset)
rand_weights = rand_weights / np.sum(rand_weights)
port_weight.append(rand_weights)
rand_returns = np.sum(dly_return.mean() * rand_weights)*365
port_ret.append(rand_returns)
rand_std = np.sqrt(np.dot(rand_weights.T, np.dot(dly_cov*365, rand_weights)))
port_std.append(rand_std.mean())
rand_sharpe = (rand_returns.mean() / rand_std.mean())
sharpe.append(rand_sharpe)
portfolio = {'Return': port_ret,
'Std': port_std,
'Sharpe': sharpe}
for i, symbol in enumerate(dly_return.columns):
portfolio[symbol+'_weight'] = [Weight[i] for Weight in port_weight]
portfolio_df = pd.DataFrame(portfolio)
column_order = ['Return', 'Std', 'Sharpe'] + [symbol+'_weight' for symbol in dly_return.columns]
portfolio_df = portfolio_df[column_order]
The resulted dataframe looks like:
Next, find optimum retururn, minimum std portfolio weights based on risk levels:
max_sharpe_port = portfolio_df[portfolio_df['Std'] == portfolio_df['Std'].min()]
min_std_port = portfolio_df[portfolio_df['Sharpe'] == portfolio_df['Sharpe'].max()]
target_std_interval = [1, 0] # select your target risk level to see the best return portfolio
maxret_by_targetstd = portfolio_df[(portfolio_df.Std < target_std_interval[0]) & (portfolio_df.Std > target_std_interval[1])].sort_values(by='Return',ascending=False).iloc[0]
Min Std Portfolio:
Return 1.121176 Std 0.802215 Sharpe 1.397600 BTC-USD_weight 0.004903 ETH-USD_weight 0.586632 BNB-USD_weight 0.408465
Max Sharpe Portfolio:
Return 0.763374 Std 0.681481 Sharpe 1.120169 BTC-USD_weight 0.865129 ETH-USD_weight 0.021218 BNB-USD_weight 0.113653
Max Return Withn 1 Std Portfilo:
Return 1.146073 Std 0.831619 Sharpe 1.378123 BTC-USD_weight 0.002784 ETH-USD_weight 0.916076 BNB-USD_weight 0.081140
Visualize all portfolios with spotted Max Sharpe and Min Std:
portfolio_df.plot.scatter(x='Std', y='Return',
c=portfolio_df['Sharpe'], cmap='YlGnBu', edgecolors='black', grid=True)
plt.scatter(x=max_sharpe_port['Std'], y=max_sharpe_port['Return'],
c='red', marker='D', s=200)
plt.scatter(x=min_std_port['Std'], y=min_std_port['Return'],
c='blue', marker='D', s=200)
plt.xlabel('Port Std Dly')
plt.ylabel('Avg Returns Dly')
plt.title('Coin Portfolio Performances')
plt.show()
Find all optimum portfolios:
from scipy.optimize import minimize
def get_ret_vol_sr(weights):
weights = np.array(weights)
ret = np.sum(dly_return.mean() * weights) * 365
std = np.sqrt(np.dot(weights.T, np.dot(dly_return.cov() * 365, weights)))
sr = ret/std
return np.array([ret, std, sr])
def portfolio_std(weights):
return get_ret_vol_sr(weights)[1]
def portfolio_return(weights):
return get_ret_vol_sr(weights)[0]
bounds = tuple((0,1) for asset in range(len(dly_return.columns)))
init_guess = len(dly_return.columns)*[1./len(dly_return.columns),]
target_returns = np.linspace(0.8, 1.1, 10)
frontier_volatility = []
for target in target_returns:
cons = ({'type':'eq','fun': lambda w: np.sum(w) - 1},
{'type':'eq','fun': lambda w: portfolio_return(w) - target})
result = minimize(portfolio_std, init_guess, method = 'SLSQP',
bounds = bounds,constraints = cons)
frontier_volatility.append(result['fun'])
Visualize all optimum portoflios as the efficient frotier:
portfolio_df.plot.scatter(x='Std', y='Return', edgecolors='black', figsize=(10,8), grid=True)
plt.scatter(x=max_sharpe_port['Std'], y=max_sharpe_port['Return'],
c='red', marker='D', s=200)
plt.scatter(x=min_std_port['Std'], y=min_std_port['Return'],
c='blue', marker='D', s=200)
plt.plot(frontier_volatility, target_returns, 'g--', linewidth = 3)
plt.xlabel('Port Std Dly)')
plt.ylabel('Avg Returns Dly')
plt.title('Coin Portfolio Performances')
plt.show()