# Start writing code here...e.g., WTF stock # how hidden cases correlated to the industry chosen (if situation is expected to be worsen, choose firm with high correlation to hidden cases)
!pip install yfinance --upgrade --no-cache-dir
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import datetime as dt
import yfinance as yf
# find the symbol (i.e., google the instrument + 'yahoo finance') to any data series you are interested at
# e.g., market/sector index ETF for your chosen country and various asset classes (e.g., Comex Gold's symbol is 'GC=F')
# e.g., SPY (https://finance.yahoo.com/quote/SPY/)
#Medical and health ETF, Zoom (remote working), Paypal (digital wallet), Bitcoin (digital currency), US bond, Bilibili (Video App)
symbols_list = ['PYPL', 'VHT','BTC-USD', 'BILI', 'ZM', 'SPY', 'FB', 'AMZN'] #, '^IRX'
start = dt.datetime(2020,3,1)
end = dt.datetime(2021,9,1)
data = yf.download(symbols_list, start=start, end=end)
# filter column adjusted close
# do not use close price as the price might be discountinous due to corporate actions
df = data['Adj Close']
# Unique names (e.g., crypto trade on weekends as well but stock do not)
df = df.ffill()
df.head()
!pip install PyPortfolioOpt==1.2.1
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
from pypfopt import cla
from pypfopt.plotting import Plotting
from pypfopt.discrete_allocation import DiscreteAllocation, get_latest_prices
from matplotlib.ticker import FuncFormatter
import seaborn as sns
from pypfopt import objective_functions
# Check NaN values in the data
nullin_df = pd.DataFrame(df,columns=symbols_list)
print(nullin_df.isnull().sum())
# Calculate portfolio mean return
mu = expected_returns.mean_historical_return(df)
print(mu)
# Calculate portfolio return variance
sigma = risk_models.sample_cov(df)
print(sigma)
# Note max sharpe ratio is the tangency portfolio
# weight bounds in negative allows shorting of stocks
ef = EfficientFrontier(mu, sigma, weight_bounds=(-1,1))
ef.add_objective(objective_functions.L2_reg, gamma=2)
# ef.add_constraint(lambda x : x <= 0.4)
# ef.add_constraint(lambda x : x >= -0.3)
# optional constraints possible, read pypfopt documentation.
sharpe_portfolio=ef.max_sharpe(risk_free_rate=0.00078) # US bond
sharpe_portfolio_wt=ef.clean_weights()
print(sharpe_portfolio_wt)
Plotting.plot_weights(sharpe_portfolio_wt)
#May use add objective to ensure minimum zero weighting to individual stocks
min_vol_portfolio=ef.min_volatility()
min_vol_portfolio_wt=ef.clean_weights()
print(min_vol_portfolio_wt)
Plotting.plot_weights(min_vol_portfolio_wt)
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(sharpe_portfolio_wt, latest_prices, total_portfolio_value=1000000)
allocation, leftover = da.greedy_portfolio()
print("Discrete allocation:", allocation)
print("Funds remaining: ${:.2f}".format(leftover))
max_sharpe_cla = cla.CLA(mu, sigma)
max_sharpe_cla.max_sharpe()
Plotting.plot_efficient_frontier(max_sharpe_cla, show_assets="True")
max_s = pd.DataFrame.from_dict(allocation,orient='index')
max_s = max_s.rename(columns={0:"no_of_stock"})
max_s["price"] = round(latest_prices,2)
max_s["value"]=round(max_s["no_of_stock"] * max_s["price"],2)
max_s
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(min_vol_portfolio_wt, latest_prices, total_portfolio_value=1000000)
allocation, leftover = da.greedy_portfolio()
print("Discrete allocation:", allocation)
print("Funds remaining: ${:.2f}".format(leftover))
min_v = pd.DataFrame.from_dict(allocation,orient='index')
min_v = min_v.rename(columns={0:"no_of_stock"})
min_v["price"] = round(latest_prices,2)
min_v["value"]=round(min_v["no_of_stock"] * min_v["price"],2)
min_v
min_vol_cla = cla.CLA(mu, sigma)
min_vol_cla.min_volatility()
Plotting.plot_efficient_frontier(min_vol_cla, show_assets="True")
sharpe_portfolio_wt_list = list(sharpe_portfolio_wt.values())
ret_data_s = df.pct_change()[1:]
weighted_returns_s = (sharpe_portfolio_wt_list * ret_data_s)
portfolio_ret_s = pd.DataFrame(weighted_returns_s.sum(axis=1))
ret_data_s = ret_data_s.merge(portfolio_ret_s, on="Date", how="left")
ret_data_s = ret_data_s.rename(columns={0: "portfolio_ret"})
ret_data_s.head()
ret_data_s['cumulative_portfolio_ret'] = (ret_data_s['portfolio_ret'] + 1).cumprod()
ret_data_s['cumulative_spy_ret'] = (ret_data_s['SPY'] + 1).cumprod()
ret_data_s.tail()
sns.scatterplot('Date', 'cumulative_portfolio_ret', data=ret_data_s)
sns.scatterplot('Date', 'cumulative_spy_ret', data=ret_data_s)
plt.legend(['cumulative_max_sharpe_portfolio_ret','cumulative_spy_ret'])
min_vol_portfolio_wt_list = list(min_vol_portfolio_wt.values())
ret_data_v = df.pct_change()[1:]
weighted_returns_v = (min_vol_portfolio_wt_list * ret_data_v)
portfolio_ret_v = pd.DataFrame(weighted_returns_v.sum(axis=1))
ret_data_v = ret_data_v.merge(portfolio_ret_v, on="Date", how="left")
ret_data_v = ret_data_v.rename(columns={0: "portfolio_ret"})
ret_data_v.head()
ret_data_v['cumulative_portfolio_ret_v'] = (ret_data_v['portfolio_ret'] + 1).cumprod()
ret_data_v['cumulative_spy_ret_v'] = (ret_data_v['SPY'] + 1).cumprod()
ret_data_v.tail()
sns.scatterplot('Date', 'cumulative_portfolio_ret_v', data=ret_data_v)
sns.scatterplot('Date', 'cumulative_spy_ret_v', data=ret_data_v)
plt.legend(['cumulative_min_vol_portfolio_ret','cumulative_spy_ret'])
import matplotlib.pyplot as plt
plt.hist(ret_data_s["portfolio_ret"], bins=50, alpha = 0.5)
plt.hist(ret_data_v["portfolio_ret"], bins=50, alpha = 0.5)
plt.legend(["Max_Sharpe", "Min_Vol"])
plt.show()
def bootstrap_replicate_1d(data, func):
# bootstrap from actual data
# draw random sample from actual data with replacement
# sample size is the number of observations of the actual data
bs_sample = np.random.choice(data, len(data))
# return the mean of a draw
return func(bs_sample)
# draw bootstrap replicates
def draw_bs_reps(data, func, size=1):
# initialize a numpy array of the dimension size
bs_replicates = np.empty(size)
# call function bootstrap_replicate_1d
for i in range(size):
bs_replicates[i] = bootstrap_replicate_1d(data,func)
# return a numpy array of the means of draws
return bs_replicates
# bootstrap from actual samples
# note that this is not sample randomly drawn from a theoretical population
bs_replicates = draw_bs_reps(ret_data_s["portfolio_ret"], np.mean, size=10000)
plt.hist(bs_replicates, bins=50, density=True, color = 'grey')
plt.xlabel('Max_Sharpe portfolio return')
plt.ylabel('X=x')
plt.title('Probability Density Function')
# Bootstrapped confidence intervals
conf_int = np.percentile(bs_replicates,[2.5, 97.5])
print('95% bootstrapped confidence interval =', conf_int, '%')
# Theoretical confidence intervals
conf_int_actual_upper = ret_data_s["portfolio_ret"].mean() + ret_data_s["portfolio_ret"].std()/np.sqrt(ret_data_s["portfolio_ret"].count())*1.96
conf_int_actual_lower = ret_data_s["portfolio_ret"].mean() - ret_data_s["portfolio_ret"].std()/np.sqrt(ret_data_s["portfolio_ret"].count())*1.96
conf_int_actual = [conf_int_actual_lower, conf_int_actual_upper]
print('-'*120)
print('95% theoretical confidence interval =', conf_int_actual, '%')
# bootstrap from actual samples
# note that this is not sample randomly drawn from a theoretical population
bs_replicates = draw_bs_reps(ret_data_v["portfolio_ret"], np.mean, size=10000)
plt.hist(bs_replicates, bins=50, density=True, color = 'grey')
plt.xlabel('Min_volatility porfolio return')
plt.ylabel('X = x')
plt.title('Probability Density Function')
# Bootstrapped confidence intervals
conf_int = np.percentile(bs_replicates,[2.5, 97.5])
print('95% bootstrapped confidence interval =', conf_int)
# Theoretical confidence intervals
conf_int_actual_upper = ret_data_v["portfolio_ret"].mean() + ret_data_v["portfolio_ret"].std()/np.sqrt(ret_data_v["portfolio_ret"].count())*1.96
conf_int_actual_lower = ret_data_v["portfolio_ret"].mean() - ret_data_v["portfolio_ret"].std()/np.sqrt(ret_data_v["portfolio_ret"].count())*1.96
conf_int_actual = [conf_int_actual_lower, conf_int_actual_upper]
print('-'*120)
print('95% theoretical confidence interval =', conf_int_actual)